[MERGE] crm, sales_team: lint modules and clean tests to prepare sales team enhancements
PURPOSE Prepare enhancements of sales team membership. SPECIFICATIONS Lint crm data and demo, notably about sales team and file organization. Also move crm menu entries declaration in a single file and remove some menu entries not imported anymore. Lint sales_team module. Linting containing * reorder fields according to their use; * order methods according to guidelines (compute, CRUD, actions, tools); * fix some docstrings and typos; * remove unused imports and dead code; * rename files according to guidelines; * move views in their own file for mail.activity and assets; * correctly name data and demo files; * rename some inner methods to ease understanding; Clean sales team tests and extract common test data from crm LINKS Task ID 2086889 (sales team enhancements) Task ID 2234698 (preparation lint) Community PR #49520 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
+13
-11
@@ -26,33 +26,35 @@
|
||||
'security/crm_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
||||
'data/crm_data.xml',
|
||||
'data/crm_stage_data.xml',
|
||||
'data/crm_lead_data.xml',
|
||||
'data/digest_data.xml',
|
||||
'data/crm_lead_prediction_data.xml',
|
||||
'data/crm_lost_reason_data.xml',
|
||||
'data/crm_stage_data.xml',
|
||||
'data/crm_team_data.xml',
|
||||
'data/digest_data.xml',
|
||||
'data/mail_data.xml',
|
||||
|
||||
'wizard/crm_lead_lost_views.xml',
|
||||
'wizard/crm_lead_to_opportunity_views.xml',
|
||||
'wizard/crm_lead_to_opportunity_mass_views.xml',
|
||||
'wizard/crm_merge_opportunities_views.xml',
|
||||
|
||||
'views/crm_templates.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/crm_views.xml',
|
||||
'views/assets.xml',
|
||||
'views/calendar_views.xml',
|
||||
'views/crm_menu_views.xml',
|
||||
'views/crm_lost_reason_views.xml',
|
||||
'views/crm_stage_views.xml',
|
||||
'views/crm_lead_views.xml',
|
||||
'views/calendar_views.xml',
|
||||
'views/digest_views.xml',
|
||||
'views/mail_activity_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/res_partner_views.xml',
|
||||
'views/utm_campaign_views.xml',
|
||||
'report/crm_activity_report_views.xml',
|
||||
'report/crm_opportunity_report_views.xml',
|
||||
'views/crm_team_views.xml',
|
||||
'views/digest_views.xml',
|
||||
'views/utm_campaign_views.xml'
|
||||
],
|
||||
'demo': [
|
||||
'data/crm_demo.xml',
|
||||
'data/crm_team_demo.xml',
|
||||
'data/mail_activity_demo.xml',
|
||||
'data/crm_lead_demo.xml',
|
||||
],
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="sales_team.salesteam_website_sales" model="crm.team" forcecreate="False">
|
||||
<field name="use_opportunities" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="sales_team.pos_sales_team" model="crm.team" forcecreate="False">
|
||||
<field name="use_opportunities" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="sales_team.ebay_sales_team" model="crm.team" forcecreate="False">
|
||||
<field name="use_opportunities" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!--default alias for leads-->
|
||||
<record id="mail_alias_lead_info" model="mail.alias">
|
||||
<field name="alias_name"></field>
|
||||
<field name="alias_model_id" ref="model_crm_lead"/>
|
||||
<field name="alias_user_id" ref="base.user_admin"/>
|
||||
<field name="alias_parent_model_id" ref="model_crm_team"/>
|
||||
</record>
|
||||
|
||||
<!-- opportunities' lost causes -->
|
||||
<record id="lost_reason_1" model="crm.lost.reason">
|
||||
<field name="name">Too expensive</field>
|
||||
</record>
|
||||
<record id="lost_reason_2" model="crm.lost.reason">
|
||||
<field name="name">We don't have people/skills</field>
|
||||
</record>
|
||||
<record id="lost_reason_3" model="crm.lost.reason">
|
||||
<field name="name">Not enough stock</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data noupdate="1">
|
||||
<!-- opportunities' lost causes -->
|
||||
<record id="lost_reason_1" model="crm.lost.reason">
|
||||
<field name="name">Too expensive</field>
|
||||
</record>
|
||||
<record id="lost_reason_2" model="crm.lost.reason">
|
||||
<field name="name">We don't have people/skills</field>
|
||||
</record>
|
||||
<record id="lost_reason_3" model="crm.lost.reason">
|
||||
<field name="name">Not enough stock</field>
|
||||
</record>
|
||||
</data></odoo>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data noupdate="1">
|
||||
<record id="sales_team.salesteam_website_sales" model="crm.team" forcecreate="False">
|
||||
<field name="use_opportunities" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="sales_team.pos_sales_team" model="crm.team" forcecreate="False">
|
||||
<field name="use_opportunities" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="sales_team.ebay_sales_team" model="crm.team" forcecreate="False">
|
||||
<field name="use_opportunities" eval="False"/>
|
||||
</record>
|
||||
</data></odoo>
|
||||
@@ -1,14 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="base.user_demo" model="res.users">
|
||||
<field name="groups_id" eval="[(4,ref('sales_team.group_sale_salesman'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="sales_team.team_sales_department" model="crm.team" forcecreate="False">
|
||||
<field name="alias_name">sales</field>
|
||||
<field name="alias_user_id" ref="base.user_admin"/>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="sales_team.crm_team_1" model="crm.team">
|
||||
@@ -1,6 +1,13 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!--default alias for leads-->
|
||||
<record id="mail_alias_lead_info" model="mail.alias">
|
||||
<field name="alias_name"></field>
|
||||
<field name="alias_model_id" ref="model_crm_lead"/>
|
||||
<field name="alias_user_id" ref="base.user_admin"/>
|
||||
<field name="alias_parent_model_id" ref="model_crm_team"/>
|
||||
</record>
|
||||
|
||||
<!-- CRM-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_lead_create" model="mail.message.subtype">
|
||||
@@ -15,7 +15,9 @@ class Team(models.Model):
|
||||
|
||||
use_leads = fields.Boolean('Leads', help="Check this box to filter and qualify incoming requests as leads before converting them into opportunities and assigning them to a salesperson.")
|
||||
use_opportunities = fields.Boolean('Pipeline', default=True, help="Check this box to manage a presales process with opportunities.")
|
||||
alias_id = fields.Many2one('mail.alias', string='Alias', ondelete="restrict", required=True, help="The email address associated with this channel. New emails received will automatically create new leads assigned to the channel.")
|
||||
alias_id = fields.Many2one(
|
||||
'mail.alias', string='Alias', ondelete="restrict", required=True,
|
||||
help="The email address associated with this channel. New emails received will automatically create new leads assigned to the channel.")
|
||||
# statistics about leads / opportunities / both
|
||||
lead_unassigned_count = fields.Integer(
|
||||
string='# Unassigned Leads', compute='_compute_lead_unassigned_count')
|
||||
@@ -118,6 +120,7 @@ class Team(models.Model):
|
||||
if not use_leads and not use_opportunities:
|
||||
return {'alias_name': False}
|
||||
return {}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# MESSAGING
|
||||
# ------------------------------------------------------------
|
||||
@@ -162,7 +165,7 @@ class Team(models.Model):
|
||||
return action
|
||||
|
||||
def _compute_dashboard_button_name(self):
|
||||
super(Team,self)._compute_dashboard_button_name()
|
||||
super(Team, self)._compute_dashboard_button_name()
|
||||
team_with_pipelines = self.filtered(lambda el: el.use_opportunities)
|
||||
team_with_pipelines.update({'dashboard_button_name': _("Pipeline")})
|
||||
|
||||
|
||||
@@ -123,11 +123,9 @@
|
||||
teams of the sales pipeline.</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="crm_opportunity_report_menu"
|
||||
name="Pipeline"
|
||||
parent="crm_menu_report"
|
||||
action="crm_opportunity_report_action"
|
||||
sequence="2"/>
|
||||
<record id="crm_opportunity_report_menu" model="ir.ui.menu">
|
||||
<field name="action" ref="crm.crm_opportunity_report_action"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_opportunity_report_action_lead" model="ir.actions.act_window">
|
||||
<field name="name">Leads Analysis</field>
|
||||
@@ -142,12 +140,8 @@
|
||||
<field name="help">This report analyses the source of your leads.</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="crm_opportunity_report_menu_lead"
|
||||
name="Leads"
|
||||
parent="crm_menu_report"
|
||||
action="crm_opportunity_report_action_lead"
|
||||
groups="crm.group_use_lead"
|
||||
sequence="1"/>
|
||||
<record id="crm_opportunity_report_menu_lead" model="ir.ui.menu">
|
||||
<field name="action" ref="crm.crm_opportunity_report_action_lead"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCase, mail_new_test_user
|
||||
from odoo.addons.sales_team.tests.common import TestSalesCommon
|
||||
from odoo.fields import Datetime
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
INCOMING_EMAIL = """Return-Path: {return_path}
|
||||
X-Original-To: {to}
|
||||
@@ -39,43 +39,21 @@ Cheers,
|
||||
Somebody."""
|
||||
|
||||
|
||||
class TestCrmCommon(SavepointCase, MailCase):
|
||||
class TestCrmCommon(TestSalesCommon, MailCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCrmCommon, cls).setUpClass()
|
||||
cls._init_mail_gateway()
|
||||
|
||||
cls.user_sales_manager = mail_new_test_user(
|
||||
cls.env, login='user_sales_manager',
|
||||
name='Martin Sales Manager', email='crm_manager@test.example.com',
|
||||
company_id=cls.env.ref("base.main_company").id,
|
||||
notification_type='inbox',
|
||||
groups='sales_team.group_sale_manager,base.group_partner_manager,crm.group_use_lead',
|
||||
)
|
||||
cls.user_sales_leads = mail_new_test_user(
|
||||
cls.env, login='user_sales_leads',
|
||||
name='Laetitia Sales Leads', email='crm_leads@test.example.com',
|
||||
company_id=cls.env.ref("base.main_company").id,
|
||||
notification_type='inbox',
|
||||
groups='sales_team.group_sale_salesman_all_leads,base.group_partner_manager,crm.group_use_lead',
|
||||
)
|
||||
cls.user_sales_salesman = mail_new_test_user(
|
||||
cls.env, login='user_sales_salesman',
|
||||
name='Orteil Sales Own', email='crm_salesman@test.example.com',
|
||||
company_id=cls.env.ref("base.main_company").id,
|
||||
notification_type='inbox',
|
||||
groups='sales_team.group_sale_salesman,crm.group_use_lead',
|
||||
)
|
||||
|
||||
cls.sales_team_1 = cls.env['crm.team'].create({
|
||||
'name': 'Test Sales Team',
|
||||
cls.sales_team_1.write({
|
||||
'alias_name': 'sales.test',
|
||||
'use_leads': True,
|
||||
'use_opportunities': True,
|
||||
'company_id': False,
|
||||
'user_id': cls.user_sales_manager.id,
|
||||
'member_ids': [(4, cls.user_sales_leads.id), (4, cls.env.ref('base.user_admin').id)],
|
||||
})
|
||||
|
||||
(cls.user_sales_manager | cls.user_sales_leads | cls.user_sales_salesman).write({
|
||||
'groups_id': [(4, cls.env.ref('crm.group_use_lead').id)]
|
||||
})
|
||||
|
||||
cls.env['crm.stage'].search([]).write({'sequence': 9999}) # ensure search will find test data first
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<menuitem
|
||||
id="menu_import_crm"
|
||||
name="Import & Synchronize"
|
||||
parent="crm_menu_root"/>
|
||||
|
||||
<record id="view_crm_meeting_search" model="ir.ui.view">
|
||||
<field name="name">calendar.event.form.inherit</field>
|
||||
<field name="model">calendar.event</field>
|
||||
|
||||
@@ -767,28 +767,6 @@ if record:
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Menu Actions
|
||||
-->
|
||||
<menuitem
|
||||
id="crm_menu_sales"
|
||||
name="Sales"
|
||||
parent="crm_menu_root"
|
||||
sequence="1"/>
|
||||
|
||||
<menuitem id="sales_team_menu_team_pipeline"
|
||||
name="Team Pipelines"
|
||||
parent="crm_menu_sales"
|
||||
action="sales_team.crm_team_salesteams_pipelines_act"
|
||||
groups="sales_team.group_sale_manager"
|
||||
sequence="4"/>
|
||||
|
||||
<menuitem id="res_partner_menu_customer"
|
||||
name="Customers"
|
||||
parent="crm_menu_sales"
|
||||
action="base.action_partner_form"
|
||||
sequence="5"/>
|
||||
|
||||
<!-- Lead Menu -->
|
||||
<record model="ir.actions.act_window" id="crm_lead_all_leads">
|
||||
<field name="name">Leads</field>
|
||||
@@ -839,12 +817,9 @@ if record:
|
||||
<field name="act_window_id" ref="crm_lead_all_leads"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="crm_menu_leads"
|
||||
name="Leads"
|
||||
parent="crm_menu_root"
|
||||
groups="crm.group_use_lead"
|
||||
action="crm_lead_all_leads"
|
||||
sequence="5"/>
|
||||
<record id="crm_menu_leads" model="ir.ui.menu">
|
||||
<field name="action" ref="crm.crm_lead_all_leads"/>
|
||||
</record>
|
||||
|
||||
<!-- 'My Pipeline' menu : Server action, act_window_views and act_windows -->
|
||||
<record model="ir.actions.act_window" id="crm_lead_opportunities">
|
||||
@@ -948,22 +923,10 @@ if record:
|
||||
<field name="act_window_id" ref="crm_lead_action_pipeline"/>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_crm_opportunities"
|
||||
action="action_your_pipeline"
|
||||
name="My Pipeline"
|
||||
parent="crm_menu_sales"
|
||||
sequence="1"/>
|
||||
|
||||
<menuitem
|
||||
id="crm.crm_menu_root"
|
||||
name="CRM"
|
||||
action="action_your_pipeline"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_crm_lead_categ"
|
||||
name="Tags"
|
||||
action="sales_team.sales_team_crm_tag_action"
|
||||
parent="menu_crm_config_lead"
|
||||
sequence="1"/>
|
||||
<record id="menu_crm_opportunities" model="ir.ui.menu">
|
||||
<field name="action" ref="crm.action_your_pipeline"/>
|
||||
</record>
|
||||
<record id="crm_menu_root" model="ir.ui.menu">
|
||||
<field name="action" ref="crm.action_your_pipeline"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -69,9 +69,7 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_crm_lost_reason"
|
||||
action="crm_lost_reason_action"
|
||||
parent="menu_crm_config_lead"
|
||||
sequence="6"/>
|
||||
<record id="menu_crm_lost_reason" model="ir.ui.menu">
|
||||
<field name="action" ref="crm.crm_lost_reason_action"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -1,26 +1,126 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<menuitem
|
||||
id="crm_menu_sales"
|
||||
name="Sales"
|
||||
parent="crm_menu_root"
|
||||
sequence="1"/>
|
||||
|
||||
<menuitem id="sales_team_menu_team_pipeline"
|
||||
name="Team Pipelines"
|
||||
parent="crm_menu_sales"
|
||||
action="crm_team_salesteams_pipelines_act"
|
||||
sequence="4"/>
|
||||
<!-- Top menu item -->
|
||||
<!--
|
||||
This menu item's purpose is to overwrite another one defined in
|
||||
the base module in order to set new groups.
|
||||
-->
|
||||
<menuitem
|
||||
id="crm_menu_root"
|
||||
name="CRM"
|
||||
web_icon="crm,static/description/icon.png"
|
||||
groups="sales_team.group_sale_salesman,sales_team.group_sale_manager"
|
||||
sequence="6"/>
|
||||
|
||||
<menuitem id="res_partner_menu_customer"
|
||||
name="Customers"
|
||||
parent="crm_menu_sales"
|
||||
action="base.action_partner_form"
|
||||
sequence="5"/>
|
||||
<!-- SALES (MAIN USER MENU) -->
|
||||
<menuitem
|
||||
id="crm_menu_sales"
|
||||
name="Sales"
|
||||
parent="crm_menu_root"
|
||||
sequence="1"/>
|
||||
<menuitem
|
||||
id="menu_crm_opportunities"
|
||||
name="My Pipeline"
|
||||
parent="crm_menu_sales"
|
||||
sequence="1"/>
|
||||
<menuitem
|
||||
id="sales_team_menu_team_pipeline"
|
||||
name="Team Pipelines"
|
||||
parent="crm_menu_sales"
|
||||
action="sales_team.crm_team_salesteams_pipelines_act"
|
||||
groups="sales_team.group_sale_manager"
|
||||
sequence="4"/>
|
||||
<menuitem
|
||||
id="res_partner_menu_customer"
|
||||
name="Customers"
|
||||
parent="crm_menu_sales"
|
||||
action="base.action_partner_form"
|
||||
sequence="5"/>
|
||||
|
||||
<menuitem id="crm_menu_leads"
|
||||
name="Leads"
|
||||
parent="crm_menu_root"
|
||||
groups="crm.group_use_lead"
|
||||
sequence="5"/>
|
||||
</odoo>
|
||||
<!-- LEADS (MAIN USER MENU) -->
|
||||
<menuitem
|
||||
id="crm_menu_leads"
|
||||
name="Leads"
|
||||
parent="crm_menu_root"
|
||||
groups="crm.group_use_lead"
|
||||
sequence="5"/>
|
||||
|
||||
<!-- REPORTING -->
|
||||
<menuitem
|
||||
id="crm_menu_report"
|
||||
name="Reporting"
|
||||
parent="crm_menu_root"
|
||||
sequence="20"
|
||||
groups="sales_team.group_sale_manager"/>
|
||||
<menuitem
|
||||
id="crm_opportunity_report_menu_lead"
|
||||
name="Leads"
|
||||
parent="crm_menu_report"
|
||||
groups="crm.group_use_lead"
|
||||
sequence="1"/>
|
||||
<menuitem
|
||||
id="crm_opportunity_report_menu"
|
||||
name="Pipeline"
|
||||
parent="crm_menu_report"
|
||||
sequence="2"/>
|
||||
|
||||
<!-- CONFIGURATION -->
|
||||
<menuitem
|
||||
id="crm_menu_config"
|
||||
name="Configuration"
|
||||
parent="crm_menu_root"
|
||||
sequence="25" groups="sales_team.group_sale_manager"/>
|
||||
<menuitem
|
||||
id="crm_config_settings_menu"
|
||||
name="Settings"
|
||||
parent="crm_menu_config"
|
||||
groups="base.group_system"
|
||||
sequence="0"/>
|
||||
<menuitem
|
||||
id="menu_crm_config_opportunity"
|
||||
name="Opportunities"
|
||||
parent="crm_menu_config"
|
||||
sequence="1"
|
||||
groups="sales_team.group_sale_manager"/>
|
||||
<menuitem
|
||||
id="crm_team_config"
|
||||
name="Sales Teams"
|
||||
parent="crm_menu_config"
|
||||
action="sales_team.sales_team_config_action"
|
||||
sequence="5"/>
|
||||
<menuitem
|
||||
id="crm_team_menu_config_activity_types"
|
||||
name="Activity Types"
|
||||
parent="crm_menu_config"
|
||||
action="sales_team.mail_activity_type_action_config_sales"
|
||||
sequence="10"/>
|
||||
<menuitem
|
||||
id="menu_crm_config_lead"
|
||||
name="Pipeline"
|
||||
parent="crm_menu_config"
|
||||
sequence="15"
|
||||
groups="sales_team.group_sale_manager"/>
|
||||
<menuitem
|
||||
id="menu_crm_lead_stage_act"
|
||||
name="Stages"
|
||||
sequence="0"
|
||||
parent="menu_crm_config_lead"
|
||||
groups="base.group_no_one"/>
|
||||
<menuitem
|
||||
id="menu_crm_lead_categ"
|
||||
name="Tags"
|
||||
action="sales_team.sales_team_crm_tag_action"
|
||||
parent="menu_crm_config_lead"
|
||||
sequence="1"/>
|
||||
<menuitem
|
||||
id="menu_crm_lost_reason"
|
||||
name="Lost Reasons"
|
||||
parent="menu_crm_config_lead"
|
||||
sequence="6"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_import_crm"
|
||||
name="Import & Synchronize"
|
||||
parent="crm_menu_root"/>
|
||||
</odoo>
|
||||
|
||||
@@ -71,12 +71,8 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_crm_lead_stage_act"
|
||||
name="Stages"
|
||||
sequence="0"
|
||||
action="crm_stage_action"
|
||||
parent="menu_crm_config_lead"
|
||||
groups="base.group_no_one"/>
|
||||
<record id="menu_crm_lead_stage_act" model="ir.ui.menu">
|
||||
<field name="action" ref="crm.crm_stage_action"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -268,9 +268,4 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sales_team.mail_activity_type_action_config_sales" model="ir.actions.act_window">
|
||||
<field name="domain">['|', ('res_model_id', '=', False), ('res_model_id.model', 'in', ['crm.lead', 'res.partner'])]</field>
|
||||
<field name="context">{'default_res_model': 'crm.lead'}</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Top menu item -->
|
||||
<!--
|
||||
This menu item's purpose is to overwrite another one defined in
|
||||
the base module in order to set new groups.
|
||||
-->
|
||||
<menuitem
|
||||
id="crm_menu_root"
|
||||
name="CRM"
|
||||
web_icon="crm,static/description/icon.png"
|
||||
groups="sales_team.group_sale_salesman,sales_team.group_sale_manager"
|
||||
sequence="6"/>
|
||||
|
||||
<menuitem
|
||||
id="crm_menu_config"
|
||||
name="Configuration"
|
||||
parent="crm_menu_root"
|
||||
sequence="25" groups="sales_team.group_sale_manager"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_crm_config_lead"
|
||||
name="Pipeline"
|
||||
parent="crm_menu_config"
|
||||
sequence="15"
|
||||
groups="sales_team.group_sale_manager"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_crm_config_opportunity"
|
||||
name="Opportunities"
|
||||
parent="crm_menu_config"
|
||||
sequence="1"
|
||||
groups="sales_team.group_sale_manager"/>
|
||||
|
||||
<menuitem id="crm_config_settings_menu"
|
||||
name="Settings"
|
||||
parent="crm_menu_config"
|
||||
action="crm_config_settings_action"
|
||||
groups="base.group_system"
|
||||
sequence="0"/>
|
||||
|
||||
<menuitem id="crm_menu_report"
|
||||
name="Reporting"
|
||||
parent="crm_menu_root"
|
||||
sequence="20"
|
||||
groups="sales_team.group_sale_manager"/>
|
||||
|
||||
<menuitem id="crm_team_config"
|
||||
name="Sales Teams"
|
||||
parent="crm_menu_config"
|
||||
action="sales_team.sales_team_config_action"
|
||||
sequence="5"/>
|
||||
|
||||
<menuitem id="crm_team_menu_config_activity_types"
|
||||
name="Activity Types"
|
||||
parent="crm_menu_config"
|
||||
action="sales_team.mail_activity_type_action_config_sales"
|
||||
sequence="10"/>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="sales_team.mail_activity_type_action_config_sales" model="ir.actions.act_window">
|
||||
<field name="domain">['|', ('res_model_id', '=', False), ('res_model_id.model', 'in', ['crm.lead', 'res.partner'])]</field>
|
||||
<field name="context">{'default_res_model': 'crm.lead'}</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -133,4 +133,9 @@
|
||||
<field name="target">inline</field>
|
||||
<field name="context">{'module' : 'crm'}</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_config_settings_menu" model="ir.ui.menu">
|
||||
<field name="action" ref="crm.crm_config_settings_action"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Sales Teams',
|
||||
'version': '1.0',
|
||||
'category': 'Sales/Sales',
|
||||
'summary': 'Sales Teams',
|
||||
'description': """
|
||||
Using this application you can manage Sales Teams with CRM and/or Sales
|
||||
===========================================================================
|
||||
Using this application you can manage Sales Teams with CRM and/or Sales
|
||||
=======================================================================
|
||||
""",
|
||||
'website': 'https://www.odoo.com/page/crm',
|
||||
'depends': ['base', 'mail'],
|
||||
'data': ['security/sales_team_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/sales_team_data.xml',
|
||||
'views/crm_team_views.xml',
|
||||
'views/sales_team_dashboard.xml',
|
||||
'views/crm_tag_views.xml',
|
||||
],
|
||||
'demo': ['data/sales_team_demo.xml',
|
||||
'data/crm_tag_demo.xml',
|
||||
],
|
||||
'data': [
|
||||
'security/sales_team_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/crm_team_data.xml',
|
||||
'views/assets.xml',
|
||||
'views/crm_tag_views.xml',
|
||||
'views/crm_team_views.xml',
|
||||
'views/mail_activity_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/crm_team_demo.xml',
|
||||
'data/crm_tag_demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<odoo><data noupdate="1">
|
||||
|
||||
<!-- crm categories -->
|
||||
<record id="categ_oppor1" model="crm.tag">
|
||||
<field name="name">Product</field>
|
||||
<field name="color" eval="1"/>
|
||||
</record>
|
||||
<record id="categ_oppor2" model="crm.tag">
|
||||
<field name="name">Software</field>
|
||||
<field name="color" eval="2"/>
|
||||
</record>
|
||||
<record id="categ_oppor3" model="crm.tag">
|
||||
<field name="name">Services</field>
|
||||
<field name="color" eval="3"/>
|
||||
</record>
|
||||
<record id="categ_oppor4" model="crm.tag">
|
||||
<field name="name">Information</field>
|
||||
<field name="color" eval="4"/>
|
||||
</record>
|
||||
<record id="categ_oppor5" model="crm.tag">
|
||||
<field name="name">Design</field>
|
||||
<field name="color" eval="5"/>
|
||||
</record>
|
||||
<record id="categ_oppor6" model="crm.tag">
|
||||
<field name="name">Training</field>
|
||||
<field name="color" eval="6"/>
|
||||
</record>
|
||||
<record id="categ_oppor7" model="crm.tag">
|
||||
<field name="name">Consulting</field>
|
||||
<field name="color" eval="7"/>
|
||||
</record>
|
||||
<record id="categ_oppor8" model="crm.tag">
|
||||
<field name="name">Other</field>
|
||||
<field name="color" eval="8"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
<!-- crm categories -->
|
||||
<record id="categ_oppor1" model="crm.tag">
|
||||
<field name="name">Product</field>
|
||||
<field name="color" eval="1"/>
|
||||
</record>
|
||||
<record id="categ_oppor2" model="crm.tag">
|
||||
<field name="name">Software</field>
|
||||
<field name="color" eval="2"/>
|
||||
</record>
|
||||
<record id="categ_oppor3" model="crm.tag">
|
||||
<field name="name">Services</field>
|
||||
<field name="color" eval="3"/>
|
||||
</record>
|
||||
<record id="categ_oppor4" model="crm.tag">
|
||||
<field name="name">Information</field>
|
||||
<field name="color" eval="4"/>
|
||||
</record>
|
||||
<record id="categ_oppor5" model="crm.tag">
|
||||
<field name="name">Design</field>
|
||||
<field name="color" eval="5"/>
|
||||
</record>
|
||||
<record id="categ_oppor6" model="crm.tag">
|
||||
<field name="name">Training</field>
|
||||
<field name="color" eval="6"/>
|
||||
</record>
|
||||
<record id="categ_oppor7" model="crm.tag">
|
||||
<field name="name">Consulting</field>
|
||||
<field name="color" eval="7"/>
|
||||
</record>
|
||||
<record id="categ_oppor8" model="crm.tag">
|
||||
<field name="name">Other</field>
|
||||
<field name="color" eval="8"/>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
|
||||
from babel.dates import format_date
|
||||
from datetime import date, datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import json
|
||||
|
||||
from babel.dates import format_date
|
||||
from datetime import date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.release import version
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
||||
|
||||
|
||||
class CrmTeam(models.Model):
|
||||
@@ -38,32 +37,29 @@ class CrmTeam(models.Model):
|
||||
name = fields.Char('Sales Team', required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence', default=10)
|
||||
active = fields.Boolean(default=True, help="If the active field is set to false, it will allow you to hide the Sales Team without removing it.")
|
||||
company_id = fields.Many2one('res.company', string='Company',
|
||||
default=lambda self: self.env.company, index=True)
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', index=True,
|
||||
default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(
|
||||
"res.currency", related='company_id.currency_id',
|
||||
string="Currency", readonly=True)
|
||||
"res.currency", string="Currency",
|
||||
related='company_id.currency_id', readonly=True)
|
||||
user_id = fields.Many2one('res.users', string='Team Leader', check_company=True)
|
||||
# memberships
|
||||
member_ids = fields.One2many(
|
||||
'res.users', 'sale_team_id', string='Channel Members', check_company=True,
|
||||
domain=[('share', '=', False)],
|
||||
'res.users', 'sale_team_id', string='Channel Members',
|
||||
check_company=True, domain=[('share', '=', False)],
|
||||
help="Add members to automatically assign their documents to this sales team. You can only be member of one team.")
|
||||
# UX options
|
||||
color = fields.Integer(string='Color Index', help="The color of the channel")
|
||||
favorite_user_ids = fields.Many2many(
|
||||
'res.users', 'team_favorite_user_rel', 'team_id', 'user_id',
|
||||
string='Favorite Members',
|
||||
default=_get_default_favorite_user_ids)
|
||||
string='Favorite Members', default=_get_default_favorite_user_ids)
|
||||
is_favorite = fields.Boolean(
|
||||
string='Show on dashboard',
|
||||
compute='_compute_is_favorite', inverse='_inverse_is_favorite',
|
||||
string='Show on dashboard', compute='_compute_is_favorite', inverse='_inverse_is_favorite',
|
||||
help="Favorite teams to display them in the dashboard and access them easily.")
|
||||
color = fields.Integer(string='Color Index', help="The color of the channel")
|
||||
dashboard_button_name = fields.Char(string="Dashboard Button", compute='_compute_dashboard_button_name')
|
||||
dashboard_graph_data = fields.Text(compute='_compute_dashboard_graph')
|
||||
|
||||
def _compute_dashboard_graph(self):
|
||||
for team in self:
|
||||
team.dashboard_graph_data = json.dumps(team._get_graph())
|
||||
|
||||
def _compute_is_favorite(self):
|
||||
for team in self:
|
||||
team.is_favorite = self.env.user in team.favorite_user_ids
|
||||
@@ -75,6 +71,65 @@ class CrmTeam(models.Model):
|
||||
(sudoed_self - to_fav).write({'favorite_user_ids': [(3, self.env.uid)]})
|
||||
return True
|
||||
|
||||
def _compute_dashboard_button_name(self):
|
||||
""" Sets the adequate dashboard button name depending on the Sales Team's options
|
||||
"""
|
||||
for team in self:
|
||||
team.dashboard_button_name = _("Big Pretty Button :)") # placeholder
|
||||
|
||||
def _compute_dashboard_graph(self):
|
||||
for team in self:
|
||||
team.dashboard_graph_data = json.dumps(team._get_dashboard_graph_data())
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# CRUD
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
team = super(CrmTeam, self.with_context(mail_create_nosubscribe=True)).create(values)
|
||||
if values.get('member_ids'):
|
||||
team._add_members_to_favorites()
|
||||
return team
|
||||
|
||||
def write(self, values):
|
||||
res = super(CrmTeam, self).write(values)
|
||||
if values.get('member_ids'):
|
||||
self._add_members_to_favorites()
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
default_teams = [
|
||||
self.env.ref('sales_team.salesteam_website_sales'),
|
||||
self.env.ref('sales_team.pos_sales_team'),
|
||||
self.env.ref('sales_team.ebay_sales_team')
|
||||
]
|
||||
for team in self:
|
||||
if team in default_teams:
|
||||
raise UserError(_('Cannot delete default team "%s"' % (team.name)))
|
||||
return super(CrmTeam,self).unlink()
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACTIONS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def action_primary_channel_button(self):
|
||||
""" Skeleton function to be overloaded It will return the adequate action
|
||||
depending on the Sales Team's options. """
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# TOOLS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _add_members_to_favorites(self):
|
||||
for team in self:
|
||||
team.favorite_user_ids = [(4, member.id) for member in team.member_ids]
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# GRAPH
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _graph_get_model(self):
|
||||
""" skeleton function defined here because it'll be called by crm and/or sale
|
||||
"""
|
||||
@@ -146,7 +201,7 @@ class CrmTeam(models.Model):
|
||||
self._cr.execute(query, [self.id, start_date, end_date] + where_clause_params)
|
||||
return self.env.cr.dictfetchall()
|
||||
|
||||
def _get_graph(self):
|
||||
def _get_dashboard_graph_data(self):
|
||||
def get_week_name(start_date, locale):
|
||||
""" Generates a week name (string) from a datetime according to the locale:
|
||||
E.g.: locale start_date (datetime) return string
|
||||
@@ -183,43 +238,3 @@ class CrmTeam(models.Model):
|
||||
[graph_title, graph_key] = self._graph_title_and_key()
|
||||
color = '#875A7B' if '+e' in version else '#7c7bad'
|
||||
return [{'values': values, 'area': True, 'title': graph_title, 'key': graph_key, 'color': color}]
|
||||
|
||||
def _compute_dashboard_button_name(self):
|
||||
""" Sets the adequate dashboard button name depending on the Sales Team's options
|
||||
"""
|
||||
for team in self:
|
||||
team.dashboard_button_name = _("Big Pretty Button :)") # placeholder
|
||||
|
||||
def action_primary_channel_button(self):
|
||||
""" skeleton function to be overloaded
|
||||
It will return the adequate action depending on the Sales Team's options
|
||||
"""
|
||||
return False
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
team = super(CrmTeam, self.with_context(mail_create_nosubscribe=True)).create(values)
|
||||
if values.get('member_ids'):
|
||||
team._add_members_to_favorites()
|
||||
return team
|
||||
|
||||
def write(self, values):
|
||||
res = super(CrmTeam, self).write(values)
|
||||
if values.get('member_ids'):
|
||||
self._add_members_to_favorites()
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
default_teams = [
|
||||
self.env.ref('sales_team.salesteam_website_sales'),
|
||||
self.env.ref('sales_team.pos_sales_team'),
|
||||
self.env.ref('sales_team.ebay_sales_team')
|
||||
]
|
||||
for team in self:
|
||||
if team in default_teams:
|
||||
raise UserError(_('Cannot delete default team "%s"' % (team.name)))
|
||||
return super(CrmTeam,self).unlink()
|
||||
|
||||
def _add_members_to_favorites(self):
|
||||
for team in self:
|
||||
team.favorite_user_ids = [(4, member.id) for member in team.member_ids]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
from . import test_default_team
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import test_sales_team_membership
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
class TestSalesCommon(SavepointCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestSalesCommon, cls).setUpClass()
|
||||
|
||||
cls.company_main = cls.env.user.company_id
|
||||
cls.user_sales_manager = mail_new_test_user(
|
||||
cls.env, login='user_sales_manager',
|
||||
name='Martin Sales Manager', email='crm_manager@test.example.com',
|
||||
company_id=cls.company_main.id,
|
||||
notification_type='inbox',
|
||||
groups='sales_team.group_sale_manager,base.group_partner_manager',
|
||||
)
|
||||
cls.user_sales_leads = mail_new_test_user(
|
||||
cls.env, login='user_sales_leads',
|
||||
name='Laetitia Sales Leads', email='crm_leads@test.example.com',
|
||||
company_id=cls.company_main.id,
|
||||
notification_type='inbox',
|
||||
groups='sales_team.group_sale_salesman_all_leads,base.group_partner_manager',
|
||||
)
|
||||
cls.user_sales_salesman = mail_new_test_user(
|
||||
cls.env, login='user_sales_salesman',
|
||||
name='Orteil Sales Own', email='crm_salesman@test.example.com',
|
||||
company_id=cls.company_main.id,
|
||||
notification_type='inbox',
|
||||
groups='sales_team.group_sale_salesman',
|
||||
)
|
||||
|
||||
cls.env['crm.team'].search([]).write({'sequence': 9999})
|
||||
cls.sales_team_1 = cls.env['crm.team'].create({
|
||||
'name': 'Test Sales Team',
|
||||
'sequence': 5,
|
||||
'company_id': False,
|
||||
'user_id': cls.user_sales_manager.id,
|
||||
'member_ids': [(4, cls.user_sales_leads.id), (4, cls.env.ref('base.user_admin').id)],
|
||||
})
|
||||
|
||||
|
||||
class TestSalesMC(TestSalesCommon):
|
||||
""" Multi Company / Multi Sales Team environment """
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestSalesMC, cls).setUpClass()
|
||||
cls.company_2 = cls.env['res.company'].create({
|
||||
'name': 'New Test Company',
|
||||
'email': 'company.2@test.example.com',
|
||||
'country_id': cls.env.ref('base.fr'),
|
||||
})
|
||||
cls.team_c2 = cls.env['crm.team'].create({
|
||||
'name': 'C2 Team1',
|
||||
'sequence': 1,
|
||||
'company_id': cls.company_2.id,
|
||||
})
|
||||
@@ -1,53 +0,0 @@
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class TestDefaultTeam(common.SavepointCase):
|
||||
"""Tests to check if correct default team is found."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Set up data for default team tests."""
|
||||
super(TestDefaultTeam, cls).setUpClass()
|
||||
cls.CrmTeam = cls.env['crm.team']
|
||||
ResUsers = cls.env['res.users'].with_context(
|
||||
{'no_reset_password': True})
|
||||
group_sale_manager = cls.env.ref('sales_team.group_sale_manager')
|
||||
cls.user = ResUsers.create({
|
||||
'name': 'Team User',
|
||||
'login': 'sales_team_user',
|
||||
'email': 'sales.team.user@example.com',
|
||||
'groups_id': [(6, 0, [group_sale_manager.id])]
|
||||
})
|
||||
cls.team_1 = cls.env['crm.team'].create({
|
||||
'name': 'Test Team',
|
||||
'member_ids': [(4, cls.user.id)],
|
||||
'company_id': False
|
||||
})
|
||||
# Europe Team (fall back team)
|
||||
cls.team_2 = cls.env.ref('sales_team.team_sales_department')
|
||||
cls.team_3 = cls.env['crm.team'].create({
|
||||
'name': 'Team 3',
|
||||
'sequence': 2,
|
||||
'company_id': False
|
||||
})
|
||||
|
||||
def test_01_user_team(self):
|
||||
"""Get default team, when user belongs to one."""
|
||||
team = self.CrmTeam.with_user(self.user)._get_default_team_id()
|
||||
self.assertEqual(team, self.team_1)
|
||||
|
||||
def test_02_fallback_team(self):
|
||||
"""Get default team when user does not belong to any team.
|
||||
|
||||
Case 1: fall back default team (from XML ref) is active.
|
||||
Case 2: fall back default team is not active.
|
||||
"""
|
||||
# Clear users from team.
|
||||
self.team_1.member_ids = [(5,)]
|
||||
# Case 1.
|
||||
team = self.CrmTeam.with_user(self.user)._get_default_team_id()
|
||||
self.assertEqual(team, self.team_2)
|
||||
# Case 2. If default team is not active, fall back is the next team with the higher sequence
|
||||
self.team_2.active = False
|
||||
team = self.CrmTeam.with_user(self.user)._get_default_team_id()
|
||||
self.assertEqual(team, self.team_3)
|
||||
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.sales_team.tests.common import TestSalesMC
|
||||
|
||||
|
||||
class TestDefaultTeam(TestSalesMC):
|
||||
"""Tests to check if correct default team is found."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Set up data for default team tests."""
|
||||
super(TestDefaultTeam, cls).setUpClass()
|
||||
|
||||
cls.team_sequence = cls.env['crm.team'].create({
|
||||
'name': 'Team LowSequence',
|
||||
'sequence': 0,
|
||||
'company_id': False,
|
||||
})
|
||||
cls.team_responsible = cls.env['crm.team'].create({
|
||||
'name': 'Team 3',
|
||||
'user_id': cls.user_sales_manager.id,
|
||||
'sequence': 3,
|
||||
'company_id': cls.company_main.id
|
||||
})
|
||||
|
||||
def test_default_team_member(self):
|
||||
with self.with_user('user_sales_leads'):
|
||||
team = self.env['crm.team']._get_default_team_id()
|
||||
self.assertEqual(team, self.sales_team_1)
|
||||
|
||||
# responsible with lower sequence better than member with higher sequence
|
||||
self.team_responsible.user_id = self.user_sales_leads.id
|
||||
with self.with_user('user_sales_leads'):
|
||||
team = self.env['crm.team']._get_default_team_id()
|
||||
self.assertEqual(team, self.team_responsible)
|
||||
|
||||
def test_default_team_fallback(self):
|
||||
""" Test fallback: domain, order """
|
||||
self.sales_team_1.member_ids = [(5,)]
|
||||
|
||||
with self.with_user('user_sales_leads'):
|
||||
team = self.env['crm.team']._get_default_team_id()
|
||||
self.assertEqual(team, self.team_sequence)
|
||||
|
||||
# next one is team_responsible with sequence = 3 (team_c2 is in another company)
|
||||
self.team_sequence.active = False
|
||||
with self.with_user('user_sales_leads'):
|
||||
team = self.env['crm.team']._get_default_team_id()
|
||||
self.assertEqual(team, self.team_responsible)
|
||||
|
||||
self.user_sales_leads.write({
|
||||
'company_ids': [(4, self.company_2.id)],
|
||||
'company_id': self.company_2.id,
|
||||
})
|
||||
# multi company: switch company
|
||||
self.user_sales_leads.write({'company_id': self.company_2.id})
|
||||
with self.with_user('user_sales_leads'):
|
||||
team = self.env['crm.team']._get_default_team_id()
|
||||
self.assertEqual(team, self.team_c2)
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="assets_backend" name="sales_team assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" type="text/scss" href="/sales_team/static/src/scss/sales_team_dashboard.scss"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -1,182 +1,244 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<odoo><data>
|
||||
|
||||
<!-- Sales Teams Search view -->
|
||||
<record id="crm_team_salesteams_search" model="ir.ui.view">
|
||||
<field name="name">Sales Teams - Search</field>
|
||||
<field name="model">crm.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Salesteams Search">
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Team Leader" name="team_leader" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record id="crm_team_salesteams_search" model="ir.ui.view">
|
||||
<field name="name">Sales Teams - Search</field>
|
||||
<field name="model">crm.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Salesteams Search">
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Team Leader" name="team_leader" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Sales Teams Action -->
|
||||
<record id="crm_team_salesteams_act" model="ir.actions.act_window">
|
||||
<field name="name">Sales Teams</field>
|
||||
<field name="res_model">crm.team</field>
|
||||
<field name="view_mode">kanban,form</field>
|
||||
<field name="view_id" ref="crm_team_salesteams_search"/>
|
||||
<field name="context">{'in_sales_app': True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new sales team
|
||||
</p><p>
|
||||
Use Sales Teams to organize your sales departments.
|
||||
Each team will work with a separate pipeline.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_team_salesteams_pipelines_act" model="ir.actions.act_window">
|
||||
<field name="name">Team Pipelines</field>
|
||||
<field name="res_model">crm.team</field>
|
||||
<field name="view_mode">kanban,form</field>
|
||||
<field name="view_id" ref="crm_team_salesteams_search"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new sales team
|
||||
</p><p>
|
||||
Use Sales Teams to organize your sales departments.
|
||||
Each team will work with a separate pipeline.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Sales Teams Form View -->
|
||||
|
||||
<record id="crm_team_view_form" model="ir.ui.view">
|
||||
<field name="name">crm.team.form</field>
|
||||
<field name="model">crm.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Sales Team">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box"/>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only" string="Sales Team"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="Sales Team name..."/>
|
||||
</h1>
|
||||
<div name="options_active" />
|
||||
</div>
|
||||
<group>
|
||||
<group name="left">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="user_id" domain="[('share', '=', False)]"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group>
|
||||
<group name="right">
|
||||
</group>
|
||||
<record id="crm_team_view_form" model="ir.ui.view">
|
||||
<field name="name">crm.team.form</field>
|
||||
<field name="model">crm.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Sales Team">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box"/>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only" string="Sales Team"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="Sales Team name..."/>
|
||||
</h1>
|
||||
<div name="options_active" />
|
||||
</div>
|
||||
<group>
|
||||
<group name="left">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="user_id" domain="[('share', '=', False)]"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="members" string="Team Members" >
|
||||
<field name="member_ids" widget="many2many" options="{'not_delete': True}">
|
||||
<kanban quick_create="false" create="true" delete="true">
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click" style="max-width: 200px">
|
||||
<div class="o_kanban_record_top">
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.id.raw_value)" class="oe_avatar oe_kanban_avatar_smallbox o_image_40_cover mb0" alt="Avatar"/>
|
||||
<div class="o_kanban_record_headings ml8">
|
||||
<strong class="o_kanban_record_title"><field name="name"/></strong>
|
||||
</div>
|
||||
<group name="right">
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="members" string="Team Members" >
|
||||
<field name="member_ids" widget="many2many" options="{'not_delete': True}">
|
||||
<kanban quick_create="false" create="true" delete="true">
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click" style="max-width: 200px">
|
||||
<div class="o_kanban_record_top">
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.id.raw_value)" class="oe_avatar oe_kanban_avatar_smallbox o_image_40_cover mb0" alt="Avatar"/>
|
||||
<div class="o_kanban_record_headings ml8">
|
||||
<strong class="o_kanban_record_title"><field name="name"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers" help="Follow this salesteam to automatically track the events associated to users of this team."/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers" help="Follow this salesteam to automatically track the events associated to users of this team."/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Sales Teams Tree View -->
|
||||
<record id="crm_team_view_tree" model="ir.ui.view">
|
||||
<field name="name">crm.team.tree</field>
|
||||
<field name="model">crm.team</field>
|
||||
<field name="field_parent">child_ids</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Sales Team">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="user_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="crm_team_view_tree" model="ir.ui.view">
|
||||
<field name="name">crm.team.tree</field>
|
||||
<field name="model">crm.team</field>
|
||||
<field name="field_parent">child_ids</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Sales Team">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="user_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Sales Teams Kanban View -->
|
||||
<record id="crm_team_view_kanban" model="ir.ui.view">
|
||||
<field name="name">crm.team.kanban</field>
|
||||
<field name="model">crm.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_content oe_kanban_global_click">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<strong><field name="name"/></strong>
|
||||
<record id="crm_team_view_kanban" model="ir.ui.view">
|
||||
<field name="name">crm.team.kanban</field>
|
||||
<field name="model">crm.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_content oe_kanban_global_click">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<strong><field name="name"/></strong>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<span class="float-right"><field name="user_id"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Case Teams Salesteams dashboard view -->
|
||||
<record id="crm_team_salesteams_view_kanban" model="ir.ui.view" >
|
||||
<field name="name">crm.team.dashboard</field>
|
||||
<field name="model">crm.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="oe_background_grey o_kanban_dashboard o_salesteam_kanban" create="0">
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<field name="member_ids"/>
|
||||
<field name="color"/>
|
||||
<field name="currency_id"/>
|
||||
<field name="is_favorite"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="#{!selection_mode ? kanban_color(record.color.raw_value) : ''}">
|
||||
<div t-attf-class="o_kanban_card_header">
|
||||
<div class="o_kanban_card_header_title">
|
||||
<div class="o_primary"><field name="name"/></div>
|
||||
</div>
|
||||
<div class="o_kanban_manage_button_section">
|
||||
<a class="o_kanban_manage_toggle_button" href="#"><i class="fa fa-ellipsis-v" role="img" aria-label="Manage" title="Manage"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container o_kanban_card_content">
|
||||
<div class="row o_kanban_card_upper_content">
|
||||
<div class="col-5 o_kanban_primary_left" name="to_replace_in_sale_crm">
|
||||
<button type="object" class="btn btn-primary" name="action_primary_channel_button"><field name="dashboard_button_name"/></button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<span class="float-right"><field name="user_id"/></span>
|
||||
<div class="col-7 o_kanban_primary_right" style="padding-bottom:0;">
|
||||
<t name="first_options"/>
|
||||
<t name="second_options"/>
|
||||
<t name="third_options"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 o_kanban_primary_bottom">
|
||||
<t t-call="SalesTeamDashboardGraph"/>
|
||||
</div>
|
||||
<div class="col-12 o_kanban_primary_bottom bottom_block">
|
||||
</div>
|
||||
</div>
|
||||
</div><div class="container o_kanban_card_manage_pane dropdown-menu" role="menu">
|
||||
<div class="row">
|
||||
<div class="col-4 o_kanban_card_manage_section o_kanban_manage_view">
|
||||
<div role="menuitem" class="o_kanban_card_manage_title">
|
||||
<span>View</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 o_kanban_card_manage_section o_kanban_manage_new">
|
||||
<div role="menuitem" class="o_kanban_card_manage_title">
|
||||
<span>New</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 o_kanban_card_manage_section o_kanban_manage_reports">
|
||||
<div role="menuitem" class="o_kanban_card_manage_title">
|
||||
<span>Reporting</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-if="widget.editable" class="o_kanban_card_manage_settings row" groups="sales_team.group_sale_manager">
|
||||
<div role="menuitem" aria-haspopup="true" class="col-8">
|
||||
<ul class="oe_kanban_colorpicker" data-field="color" role="menu"/>
|
||||
</div>
|
||||
<div role="menuitem" class="col-4 text-right">
|
||||
<a type="edit">Settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="SalesTeamDashboardGraph">
|
||||
<div t-if="record.dashboard_graph_data.raw_value" class="o_sales_team_kanban_graph_section">
|
||||
<field name="dashboard_graph_data" widget="dashboard_graph" t-att-graph_type="'bar'"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- add css / js -->
|
||||
<template id="assets_backend" name="sales_team assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" type="text/scss" href="/sales_team/static/src/scss/sales_team_dashboard.scss"/>
|
||||
</xpath>
|
||||
</template>
|
||||
<!-- Sales Teams Actions -->
|
||||
<record id="crm_team_salesteams_act" model="ir.actions.act_window">
|
||||
<field name="name">Sales Teams</field>
|
||||
<field name="res_model">crm.team</field>
|
||||
<field name="view_mode">kanban,form</field>
|
||||
<field name="view_id" ref="crm_team_salesteams_search"/>
|
||||
<field name="context">{'in_sales_app': True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new sales team
|
||||
</p><p>
|
||||
Use Sales Teams to organize your sales departments.
|
||||
Each team will work with a separate pipeline.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sales_team_config_action" model="ir.actions.act_window">
|
||||
<field name="name">Sales Teams</field>
|
||||
<field name="res_model">crm.team</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="view_id" ref="crm_team_salesteams_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new sales team
|
||||
</p><p>
|
||||
Use Sales Teams to organize your sales departments.
|
||||
Each team will work with a separate pipeline.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<record id="crm_team_salesteams_pipelines_act" model="ir.actions.act_window">
|
||||
<field name="name">Team Pipelines</field>
|
||||
<field name="res_model">crm.team</field>
|
||||
<field name="view_mode">kanban,form</field>
|
||||
<field name="view_id" ref="crm_team_salesteams_search"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new sales team
|
||||
</p><p>
|
||||
Use Sales Teams to organize your sales departments.
|
||||
Each team will work with a separate pipeline.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_activity_type_action_config_sales" model="ir.actions.act_window">
|
||||
<field name="name">Activity Types</field>
|
||||
<field name="res_model">mail.activity.type</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">['|', ('res_model_id', '=', False), ('res_model_id.model', '=', 'res.partner')]</field>
|
||||
</record>
|
||||
</data>
|
||||
<record id="sales_team_config_action" model="ir.actions.act_window">
|
||||
<field name="name">Sales Teams</field>
|
||||
<field name="res_model">crm.team</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="view_id" ref="crm_team_salesteams_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Define a new sales team
|
||||
</p><p>
|
||||
Use Sales Teams to organize your sales departments.
|
||||
Each team will work with a separate pipeline.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="mail_activity_type_action_config_sales" model="ir.actions.act_window">
|
||||
<field name="name">Activity Types</field>
|
||||
<field name="res_model">mail.activity.type</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">['|', ('res_model_id', '=', False), ('res_model_id.model', '=', 'res.partner')]</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,83 +0,0 @@
|
||||
<odoo>
|
||||
<!-- Case Teams Salesteams dashboard view -->
|
||||
<record id="crm_team_salesteams_view_kanban" model="ir.ui.view" >
|
||||
<field name="name">crm.team.dashboard</field>
|
||||
<field name="model">crm.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="oe_background_grey o_kanban_dashboard o_salesteam_kanban" create="0">
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<field name="member_ids"/>
|
||||
<field name="color"/>
|
||||
<field name="currency_id"/>
|
||||
<field name="is_favorite"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="#{!selection_mode ? kanban_color(record.color.raw_value) : ''}">
|
||||
<div t-attf-class="o_kanban_card_header">
|
||||
<div class="o_kanban_card_header_title">
|
||||
<div class="o_primary"><field name="name"/></div>
|
||||
</div>
|
||||
<div class="o_kanban_manage_button_section">
|
||||
<a class="o_kanban_manage_toggle_button" href="#"><i class="fa fa-ellipsis-v" role="img" aria-label="Manage" title="Manage"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container o_kanban_card_content">
|
||||
<div class="row o_kanban_card_upper_content">
|
||||
<div class="col-5 o_kanban_primary_left" name="to_replace_in_sale_crm">
|
||||
<button type="object" class="btn btn-primary" name="action_primary_channel_button"><field name="dashboard_button_name"/></button>
|
||||
</div>
|
||||
<div class="col-7 o_kanban_primary_right" style="padding-bottom:0;">
|
||||
<t name="first_options"/>
|
||||
<t name="second_options"/>
|
||||
<t name="third_options"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 o_kanban_primary_bottom">
|
||||
<t t-call="SalesTeamDashboardGraph"/>
|
||||
</div>
|
||||
<div class="col-12 o_kanban_primary_bottom bottom_block">
|
||||
</div>
|
||||
</div>
|
||||
</div><div class="container o_kanban_card_manage_pane dropdown-menu" role="menu">
|
||||
<div class="row">
|
||||
<div class="col-4 o_kanban_card_manage_section o_kanban_manage_view">
|
||||
<div role="menuitem" class="o_kanban_card_manage_title">
|
||||
<span>View</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 o_kanban_card_manage_section o_kanban_manage_new">
|
||||
<div role="menuitem" class="o_kanban_card_manage_title">
|
||||
<span>New</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 o_kanban_card_manage_section o_kanban_manage_reports">
|
||||
<div role="menuitem" class="o_kanban_card_manage_title">
|
||||
<span>Reporting</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-if="widget.editable" class="o_kanban_card_manage_settings row" groups="sales_team.group_sale_manager">
|
||||
<div role="menuitem" aria-haspopup="true" class="col-8">
|
||||
<ul class="oe_kanban_colorpicker" data-field="color" role="menu"/>
|
||||
</div>
|
||||
<div role="menuitem" class="col-4 text-right">
|
||||
<a type="edit">Settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="SalesTeamDashboardGraph">
|
||||
<div t-if="record.dashboard_graph_data.raw_value" class="o_sales_team_kanban_graph_section">
|
||||
<field name="dashboard_graph_data" widget="dashboard_graph" t-att-graph_type="'bar'"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user