[IMP] sale,crm: Notify users on online orders/leads

Purpose
=======

The user is not notified when an online order is placed or when an opportunity is posted on the contact form.

set admin as default follower of sales teams (or any team creator).

The concept of sales channel is confusign to users. When you need to create a channel, it's to manage a team of people -> rename bac kto "Sales Team"

Specification
=============

crm lead:
---------

- notify admin user when a lead/opport is created by a website visitor or any other user or created from an email sent to the email alias
- admin user should be follower of first sales team by default => Done
- "Opportunity Created" subtype should be selected by default when a user becomes a follower of any salesteam
- Show following fields in the notification:
    - Contact Name:
    - Customer Name:
    - Phone:
    - Email:
    - Message:
    - Sales Channel:
    - Stage: New
    + other current tracked fields in case of update ( expected revenue, score)
- don't show company name, contact name, phone if not set (e.g. if created from email alias)

ecommerce:
----------

- notify admin when an order is placed on the website:
- admin should be follower of website salesteam by default
- order tracked fields in a logical order and add currency to amounts (nice to have but would be cleaner):
    - Customer:
    - Salesperson:
    - Status:
    - Delivery Amount:
    - Untaxed Amount:
    - Total:
- edit notif preferences on the salesteam:
- In notifications sent by email, add a link to edit notif preferences in the salesteam: "Edit your notification preferences"
- redirect to notification preferences of the user in the salesteam

- Other users must easily manage the way they want to be notified about salesteams activities:
- move kanban view out of reporting (it's an operational view, not a reporting one and all the users need to see it)
- CRM: https://drive.google.com/a/odoo.com/file/d/1ZWgU_2uozRvwhe8dtemvEFWP_Rsq-bdn/view?usp=drivesdk
- Sales: https://drive.google.com/a/odoo.com/file/d/1AzmT2-shMXzl6olm2d_u0ZXY4Ov1m4q2/view?usp=drivesdk
- show assignation section out of debug mode, to let users edit following preferences in the sales team
- if no sales channel set for the website (https://drive.google.com/a/odoo.com/file/d/1TbB0FsdFxkTRLq-2Fa480AK9MPbCV9Q3/view?usp=drivesdk), show a specific message asking to set one: "Please set a sales team for the website. Otherwise you can't follow related events."

Miscellaneous:
--------------

- rename Sales channels into the old term "Sales Teams", the concept of sales channel is not understood by most users
This commit is contained in:
Yannick Tivisse
2018-07-25 15:45:36 +02:00
65 changed files with 183 additions and 142 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ This module allows to implement action rules for any object.
Use automated actions to automatically trigger actions for various screens.
**Example:** A lead created by a specific user may be automatically set to a specific
sales channel, or an opportunity which still has status pending after 14 days might
Sales Team, or an opportunity which still has status pending after 14 days might
trigger an automatic reminder email.
""",
'depends': ['base', 'resource', 'mail'],
@@ -84,7 +84,7 @@
</p><p>
Use automated actions to automatically trigger actions for
various screens. Example: a lead created by a specific user may
be automatically set to a specific sales channel, or an
be automatically set to a specific Sales Team, or an
opportunity which still has status pending after 14 days might
trigger an automatic reminder email.
</p>
+1 -1
View File
@@ -40,7 +40,7 @@ After creating reports in each Odoo app, you can choose to make them as Favorite
</div>
<div class="oe_span6">
<p class="oe_mt32">
In each Odoo App, you can create detailed reports and graphs in any format you like without the need of an external program. Get statistics on any numbers in your company, from fuel costs in projects all the way to revenues in sales channels, and with inventory, and everything in between.
In each Odoo App, you can create detailed reports and graphs in any format you like without the need of an external program. Get statistics on any numbers in your company, from fuel costs in projects all the way to revenues in Sales Teams, and with inventory, and everything in between.
</p>
</div>
</div>
+4 -4
View File
@@ -19,7 +19,7 @@ Lead Management Made Easy
-------------------------
Create leads automatically from incoming emails. Analyse leads efficiency and
compare performance by campaigns, channels or sales channel.
compare performance by campaigns, channels or Sales Team.
Find duplicates, merge leads and assign them to the right salesperson in one
operation. Spend less time on administration and more time on qualifying leads.
@@ -42,7 +42,7 @@ company uses Microsoft Outlook or Gmail, no one needs to change the way they
work, so everyone stays productive.
Route, sort and filter incoming emails automatically. Odoo CRM handles incoming
emails and route them to the right opportunities or sales channel. New leads are
emails and route them to the right opportunities or Sales Team. New leads are
created on the fly and interested salespersons are notified automatically.
Collaborative Agenda
@@ -80,12 +80,12 @@ Drive Engagement with Gamification
Reinforce good habits and improve win rates with real-time recognition and
rewards inspired by [game mechanics](http://en.wikipedia.org/wiki/Gamification).
Align sales channels around clear business objectives with challenges, personal
Align Sales Teams around clear business objectives with challenges, personal
objectives and team leader boards.
### Leaderboards
Promote leaders and competition amongst sales channel with performance ratios.
Promote leaders and competition amongst Sales Team with performance ratios.
### Personal Objectives
+7 -2
View File
@@ -30,10 +30,10 @@
</record>
<!-- Salesteam-related subtypes for messaging / Chatter -->
<record id="mt_salesteam_lead" model="mail.message.subtype">
<field name="name">Lead Created</field>
<field name="name">Opportunity Created</field>
<field name="sequence">10</field>
<field name="res_model">crm.team</field>
<field name="default" eval="False"/>
<field name="default" eval="True"/>
<field name="parent_id" eval="ref('mt_lead_create')"/>
<field name="relation_field">team_id</field>
</record>
@@ -60,6 +60,11 @@
<field name="relation_field">team_id</field>
</record>
<function model="crm.team" name="message_unsubscribe"
eval="[ref('sales_team.team_sales_department'),ref('sales_team.salesteam_website_sales')], [ref('base.partner_root')]"/>
<function model="crm.team" name="message_subscribe"
eval="[ref('sales_team.team_sales_department'),ref('sales_team.salesteam_website_sales')], [ref('base.partner_root')]"/>
<!--Definition of an email template with an empty body that will be used in opportunity mailing.
Used to give a basis for email recipients, name and to ease the definition of a further
elaborated template. -->
+17 -14
View File
@@ -63,23 +63,23 @@ class Lead(models.Model):
return self._stage_find(team_id=team.id, domain=[('fold', '=', False)]).id
name = fields.Char('Opportunity', required=True, index=True)
partner_id = fields.Many2one('res.partner', string='Customer', track_visibility='onchange', index=True,
partner_id = fields.Many2one('res.partner', string='Customer', track_visibility='onchange', track_sequence=1, index=True,
help="Linked partner (optional). Usually created when converting the lead.")
active = fields.Boolean('Active', default=True)
date_action_last = fields.Datetime('Last Action', readonly=True)
email_from = fields.Char('Email', help="Email address of the contact", index=True)
email_from = fields.Char('Email', help="Email address of the contact", track_visibility='onchange', track_sequence=4, index=True)
website = fields.Char('Website', index=True, help="Website of the contact")
team_id = fields.Many2one('crm.team', string='Sales Channel', oldname='section_id', default=lambda self: self.env['crm.team'].sudo()._get_default_team_id(user_id=self.env.uid),
index=True, track_visibility='onchange', help='When sending mails, the default email address is taken from the sales channel.')
team_id = fields.Many2one('crm.team', string='Sales Team', oldname='section_id', default=lambda self: self.env['crm.team'].sudo()._get_default_team_id(user_id=self.env.uid),
index=True, track_visibility='onchange', help='When sending mails, the default email address is taken from the Sales Team.')
kanban_state = fields.Selection([('grey', 'No next activity planned'), ('red', 'Next activity late'), ('green', 'Next activity is planned')],
string='Kanban State', compute='_compute_kanban_state')
email_cc = fields.Text('Global CC', help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma")
description = fields.Text('Notes')
description = fields.Text('Notes', track_visibility='onchange', track_sequence=6)
create_date = fields.Datetime('Create Date', readonly=True)
write_date = fields.Datetime('Update Date', readonly=True)
tag_ids = fields.Many2many('crm.lead.tag', 'crm_lead_tag_rel', 'lead_id', 'tag_id', string='Tags', help="Classify and analyze your lead/opportunity categories like: Training, Service")
contact_name = fields.Char('Contact Name')
partner_name = fields.Char("Customer Name", index=True, help='The name of the future partner company that will be created while converting the lead into opportunity')
contact_name = fields.Char('Contact Name', track_visibility='onchange', track_sequence=3)
partner_name = fields.Char("Customer Name", track_visibility='onchange', track_sequence=2, index=True, help='The name of the future partner company that will be created while converting the lead into opportunity')
opt_out = fields.Boolean(string='Opt-Out', oldname='optout',
help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. "
"Filter 'Available for Mass Mailing' allows users to filter the leads when performing mass mailing.")
@@ -124,7 +124,7 @@ class Lead(models.Model):
city = fields.Char('City')
state_id = fields.Many2one("res.country.state", string='State')
country_id = fields.Many2one('res.country', string='Country')
phone = fields.Char('Phone')
phone = fields.Char('Phone', track_visibility='onchange', track_sequence=5)
mobile = fields.Char('Mobile')
function = fields.Char('Job Position')
title = fields.Many2one('res.partner.title')
@@ -286,7 +286,7 @@ class Lead(models.Model):
@api.model
def create(self, vals):
# set up context used to find the lead's sales channel which is needed
# set up context used to find the lead's Sales Team which is needed
# to correctly set the default stage_id
context = dict(self._context or {})
if vals.get('type') and not self._context.get('default_type'):
@@ -647,7 +647,7 @@ class Lead(models.Model):
The resulting lead/opportunity will be the most important one (based on its confidence level)
updated with values from other opportunities to merge.
:param user_id : the id of the saleperson. If not given, will be determined by `_merge_data`.
:param team : the id of the sales channel. If not given, will be determined by `_merge_data`.
:param team : the id of the Sales Team. If not given, will be determined by `_merge_data`.
:return crm.lead record resulting of th merge
"""
if len(self.ids) <= 1:
@@ -671,7 +671,7 @@ class Lead(models.Model):
# the first (head opp) will be a priority.
merged_data = opportunities._merge_data(list(CRM_LEAD_FIELDS_TO_MERGE))
# force value for saleperson and sales channel
# force value for saleperson and Sales Team
if user_id:
merged_data['user_id'] = user_id
if team_id:
@@ -680,7 +680,7 @@ class Lead(models.Model):
# merge other data (mail.message, attachments, ...) from tail into head
opportunities_head.merge_dependences(opportunities_tail)
# check if the stage is in the stages of the sales channel. If not, assign the stage with the lowest sequence
# check if the stage is in the stages of the Sales Team. If not, assign the stage with the lowest sequence
if merged_data.get('team_id'):
team_stage_ids = self.env['crm.stage'].search(['|', ('team_id', '=', merged_data['team_id']), ('team_id', '=', False)], order='sequence')
if merged_data.get('stage_id') not in team_stage_ids.ids:
@@ -726,7 +726,7 @@ class Lead(models.Model):
def _convert_opportunity_data(self, customer, team_id=False):
""" Extract the data from a lead to create the opportunity
:param customer : res.partner record
:param team_id : identifier of the sales channel to determine the stage
:param team_id : identifier of the Sales Team to determine the stage
"""
if not team_id:
team_id = self.team_id.id if self.team_id else False
@@ -951,7 +951,7 @@ class Lead(models.Model):
return self.message_post(body=message)
# ----------------------------------------
# Sales Channel Dashboard
# Sales Team Dashboard
# ----------------------------------------
@api.model
@@ -1113,6 +1113,9 @@ class Lead(models.Model):
{'url': won_action, 'title': _('Won')},
{'url': lost_action, 'title': _('Lost')}]
if self.team_id:
salesman_actions.append({'url': self._notify_get_action_link('view', res_id=self.team_id.id, model=self.team_id._name), 'title': _('Sales Team Settings')})
new_group = (
'group_sale_salesman', lambda partner: bool(partner.user_ids) and any(user.has_group('sales_team.group_sale_salesman') for user in partner.user_ids), {
'actions': salesman_actions,
+2 -2
View File
@@ -25,7 +25,7 @@ class Stage(models.Model):
@api.model
def default_get(self, fields):
""" Hack : when going from the pipeline, creating a stage with a sales team in
context should not create a stage for the current sales channel only
context should not create a stage for the current Sales Team only
"""
ctx = dict(self.env.context)
if ctx.get('default_team_id') and not ctx.get('crm_team_mono'):
@@ -37,7 +37,7 @@ class Stage(models.Model):
probability = fields.Float('Probability (%)', required=True, default=10.0, help="This percentage depicts the default/average probability of the Case for this stage to be a success")
on_change = fields.Boolean('Change Probability Automatically', help="Setting this stage will change the probability automatically on the opportunity.")
requirements = fields.Text('Requirements', help="Enter here the internal requirements for this stage (ex: Offer sent to customer). It will appear as a tooltip over the stage's name.")
team_id = fields.Many2one('crm.team', string='Sales Channel', ondelete='set null',
team_id = fields.Many2one('crm.team', string='Sales Team', ondelete='set null',
help='Specific team that uses this stage. Other teams will not be able to see or use this stage.')
legend_priority = fields.Text('Priority Management Explanation', translate=True,
help='Explanation text to help users using the star and priority mechanism on stages or issues that are in this stage.')
+4 -4
View File
@@ -118,7 +118,7 @@ class Team(models.Model):
@api.constrains('dashboard_graph_model', 'use_opportunities')
def _check_graph_model(self):
if not self.use_opportunities and self.dashboard_graph_model == 'crm.lead':
raise ValidationError(_("You have to enable the Pipeline on your sales channel to be able to set it as a content for the graph"))
raise ValidationError(_("You have to enable the Pipeline on your Sales Team to be able to set it as a content for the graph"))
@api.multi
def write(self, vals):
@@ -136,11 +136,11 @@ class Team(models.Model):
if not user_team_id:
user_team_id = self.search([], limit=1).id
action['help'] = _("""<p class='o_view_nocontent_smiling_face'>Add new opportunities</p><p>
Looks like you are not a member of a sales channel. You should add yourself
as a member of one of the sales channel.
Looks like you are not a member of a Sales Team. You should add yourself
as a member of one of the Sales Team.
</p>""")
if user_team_id:
action['help'] += "<p>As you don't belong to any sales channel, Odoo opens the first one by default.</p>"
action['help'] += "<p>As you don't belong to any Sales Team, Odoo opens the first one by default.</p>"
action_context = safe_eval(action['context'], {'uid': self.env.uid})
if user_team_id:
+1 -1
View File
@@ -8,7 +8,7 @@ class Partner(models.Model):
_inherit = 'res.partner'
team_id = fields.Many2one('crm.team', string='Sales Channel', oldname='section_id')
team_id = fields.Many2one('crm.team', string='Sales Team', oldname='section_id')
opportunity_ids = fields.One2many('crm.lead', 'partner_id', string='Opportunities', domain=[('type', '=', 'opportunity')])
meeting_ids = fields.Many2many('calendar.event', 'calendar_event_res_partner_rel', 'res_partner_id', 'calendar_event_id', string='Meetings', copy=False)
opportunity_count = fields.Integer("Opportunity", compute='_compute_opportunity_count')
+1 -1
View File
@@ -15,7 +15,7 @@ class ActivityReport(models.Model):
date = fields.Datetime('Date', readonly=True)
author_id = fields.Many2one('res.partner', 'Created By', readonly=True)
user_id = fields.Many2one('res.users', 'Salesperson', readonly=True)
team_id = fields.Many2one('crm.team', 'Sales Channel', readonly=True)
team_id = fields.Many2one('crm.team', 'Sales Team', readonly=True)
lead_id = fields.Many2one('crm.lead', "Lead", readonly=True)
subject = fields.Char('Summary', readonly=True)
subtype_id = fields.Many2one('mail.message.subtype', 'Subtype', readonly=True)
@@ -41,7 +41,7 @@
<field name="author_id" string="Created By"/>
<group expand="1" string="Group By">
<filter string="Created By" name="group_by_salesperson" context="{'group_by':'author_id'}"/>
<filter string="Sales Channel" name="saleschannel" context="{'group_by':'team_id'}"/>
<filter string="Sales Team" name="saleschannel" context="{'group_by':'team_id'}"/>
<filter string="Country" name="country" context="{'group_by':'country_id'}" />
<filter string="Company" name="company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Stage" name="stage" context="{'group_by':'stage_id'}"/>
@@ -121,7 +121,7 @@
</group>
<group expand="1" string="Group By">
<filter string="Salesperson" name="salesperson" context="{'group_by':'user_id'}" />
<filter string="Sales Channel" name="saleschannel" context="{'group_by':'team_id'}"/>
<filter string="Sales Team" name="saleschannel" context="{'group_by':'team_id'}"/>
<filter string="City" name="city" context="{'group_by':'city'}" />
<filter string="Country" name="country" context="{'group_by':'country_id'}" />
<filter string="Company" name="company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
@@ -172,7 +172,7 @@ teams of the sales pipeline.</field>
your opportunities with information such as the expected revenue, planned cost,
missed deadlines or the number of interactions per opportunity. This report is
mainly used by the sales manager in order to periodically review the pipeline
with the the sales channel.</field>
with the the Sales Team.</field>
</record>
<record id="crm_opportunity_report_action_lead" model="ir.actions.act_window">
@@ -8,11 +8,11 @@ class NewLeadNotification(TestCrmCases):
def test_new_lead_notification(self):
""" Test newly create leads like from the website. People and channels
subscribed to the sales channel shoud be notified. """
# subscribe a partner and a channel to the sales channel with new lead subtype
subscribed to the Sales Team shoud be notified. """
# subscribe a partner and a channel to the Sales Team with new lead subtype
channel_listen = self.env['mail.channel'].create({'name': 'Listener'})
sales_team_1 = self.env['crm.team'].create({
'name': 'Test Sales Channel',
'name': 'Test Sales Team',
'alias_name': 'test_sales_team',
})
+8 -2
View File
@@ -412,7 +412,7 @@
]"/>
<group expand="0" string="Group By">
<filter string="Salesperson" name="groupby_salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Sales Channel" name="groupby_team" domain="[]" context="{'group_by':'team_id'}"/>
<filter string="Sales Team" name="groupby_team" domain="[]" context="{'group_by':'team_id'}"/>
<filter name="customer" string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Campaign" name="groupby_campaign" domain="[]" context="{'group_by':'campaign_id'}"/>
<filter string="Source" name="groupby_source" domain="[]" context="{'group_by':'source_id'}"/>
@@ -679,7 +679,7 @@
<filter string="Won" name="won" domain="['&amp;', ('active', '=', True), ('stage_id.probability', '=', 100)]"/>
<group expand="0" string="Group By" colspan="16">
<filter string="Salesperson" name="salesperson" context="{'group_by':'user_id'}"/>
<filter string="Sales Channel" name="saleschannel" context="{'group_by':'team_id'}"/>
<filter string="Sales Team" name="saleschannel" context="{'group_by':'team_id'}"/>
<filter string="Country" name="country" context="{'group_by':'country_id'}" />
<filter string="Company" name="company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter name="stage" string="Stage" context="{'group_by':'stage_id'}"/>
@@ -807,6 +807,12 @@ for rec in records:
action="base.action_partner_form"
sequence="2"/>
<menuitem id="sales_team_menu_report_crm"
name="Sales Teams"
parent="crm_menu_pipeline"
action="sales_team.crm_team_salesteams_act"
sequence="10"/>
<!-- Lead Menu -->
<record model="ir.actions.act_window" id="crm_lead_all_leads">
<field name="name">Leads</field>
+1 -1
View File
@@ -148,7 +148,7 @@
</record>
<record id="crm_team_act_tree" model="ir.actions.act_window">
<field name="name">Cases by Sales Channel</field>
<field name="name">Cases by Sales Team</field>
<field name="res_model">crm.team</field>
<field name="view_type">tree</field>
<field name="view_id" ref="sales_team.crm_team_view_tree"/>
+1 -8
View File
@@ -47,7 +47,7 @@
groups="sales_team.group_sale_manager"/>
<menuitem id="crm_team_config"
name="Sales Channels"
name="Sales Teams"
parent="crm_menu_config"
action="sales_team.sales_team_config_action"
sequence="5"/>
@@ -58,11 +58,4 @@
action="sales_team.mail_activity_type_action_config_sales"
sequence="10"/>
<menuitem id="sales_team_menu_report_crm"
name="Sales Channel Dashboard"
parent="crm_menu_report"
action="sales_team.crm_team_salesteams_act"
sequence="10"/>
</odoo>
@@ -25,7 +25,7 @@
</div>
<div class="col-xs-12 col-md-6 o_setting_box" id="crm_lead"
attrs="{'invisible': [('group_use_lead','=',False)]}"
title="Emails received to that address generate new leads not assigned to any sales channel yet. This can be made when converting them into opportunities. Incoming emails can be automatically assigned to specific sales channels. To do so, set an email alias on the sales channel."
title="Emails received to that address generate new leads not assigned to any Sales Team yet. This can be made when converting them into opportunities. Incoming emails can be automatically assigned to specific Sales Teams. To do so, set an email alias on the Sales Team."
groups="base.group_no_one">
<div class="o_setting_left_pane">
<field name="generate_lead_from_alias"/>
@@ -34,7 +34,7 @@
<label for="generate_lead_from_alias" string="Manual Assignation of Incoming Emails"/>
<div class="text-muted">
Generate leads from incoming emails and assign them
to a sales channel manually
to a Sales Team manually
</div>
<div class="content-group" attrs="{'invisible': [('generate_lead_from_alias','=',False)]}">
<div class="mt16">
+2 -2
View File
@@ -50,7 +50,7 @@ class Lead2OpportunityPartner(models.TransientModel):
], 'Conversion Action', required=True)
opportunity_ids = fields.Many2many('crm.lead', string='Opportunities')
user_id = fields.Many2one('res.users', 'Salesperson', index=True)
team_id = fields.Many2one('crm.team', 'Sales Channel', oldname='section_id', index=True)
team_id = fields.Many2one('crm.team', 'Sales Team', oldname='section_id', index=True)
@api.onchange('action')
def onchange_action(self):
@@ -174,7 +174,7 @@ class Lead2OpportunityMassConvert(models.TransientModel):
return res
user_ids = fields.Many2many('res.users', string='Salesmen')
team_id = fields.Many2one('crm.team', 'Sales Channel', index=True, oldname='section_id')
team_id = fields.Many2one('crm.team', 'Sales Team', index=True, oldname='section_id')
deduplicate = fields.Boolean('Apply deduplication', default=True, help='Merge with existing leads/opportunities of each partner')
action = fields.Selection([
('each_exist_or_create', 'Use existing partner or create'),
+1 -1
View File
@@ -34,7 +34,7 @@ class MergeOpportunity(models.TransientModel):
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 Channel', oldname='section_id', index=True)
team_id = fields.Many2one('crm.team', 'Sales Team', oldname='section_id', index=True)
@api.multi
def action_merge(self):
+1 -1
View File
@@ -80,7 +80,7 @@ performers to the entire channel and publicly recognize a job well done.
### Leaderboards
Promote leaders and competition amongst sales channel with performance ratios.
Promote leaders and competition amongst Sales Team with performance ratios.
### Personal Objectives
+1 -1
View File
@@ -134,7 +134,7 @@
</p>
<ul>
<li>We will publish the release note this week</li>
<li>The whole sales channel will be trained on the new version this Friday</li>
<li>The whole Sales Team will be trained on the new version this Friday</li>
<li>We will do a public announce to our resellers the 21th of November. We plan to show them: a description of the new features, the new distribution strategy, the new pricing and the communication plan.</li>
</ul>
<br/>
+2 -1
View File
@@ -592,7 +592,8 @@ class MailThread(models.AbstractModel):
new_value = getattr(self, col_name)
if new_value != initial_value and (new_value or initial_value): # because browse null != False
tracking = self.env['mail.tracking.value'].create_tracking_values(initial_value, new_value, col_name, col_info)
track_sequence = getattr(self._fields[col_name], 'track_sequence', 100)
tracking = self.env['mail.tracking.value'].create_tracking_values(initial_value, new_value, col_name, col_info, track_sequence)
if tracking:
tracking_value_ids.append([0, 0, tracking])
+5 -2
View File
@@ -9,6 +9,7 @@ from odoo import api, fields, models, tools
class MailTracking(models.Model):
_name = 'mail.tracking.value'
_description = 'Mail Tracking Value'
_order = 'track_sequence asc'
# TDE CLEANME: why not a m2o to ir model field ?
field = fields.Char('Changed Field', required=True, readonly=1)
@@ -31,10 +32,12 @@ class MailTracking(models.Model):
mail_message_id = fields.Many2one('mail.message', 'Message ID', required=True, index=True, ondelete='cascade')
track_sequence = fields.Integer('Tracking field sequence', readonly=1, default=100)
@api.model
def create_tracking_values(self, initial_value, new_value, col_name, col_info):
def create_tracking_values(self, initial_value, new_value, col_name, col_info, track_sequence):
tracked = True
values = {'field': col_name, 'field_desc': col_info['string'], 'field_type': col_info['type']}
values = {'field': col_name, 'field_desc': col_info['string'], 'field_type': col_info['type'], 'track_sequence': track_sequence}
if col_info['type'] in ['integer', 'float', 'char', 'text', 'datetime', 'monetary']:
values.update({
@@ -45,6 +45,7 @@
<field name="new_value_char"/>
<field name="new_value_text"/>
<field name="new_value_datetime"/>
<field name="track_sequence"/>
</group>
<group string='Related Message'>
<field name="mail_message_id"/>
+1 -1
View File
@@ -63,7 +63,7 @@ about products with the blasting fast filter accross all your products.
Integrated Inventory Management
-------------------------------
Consolidate all your sales channels in real time: stores, ecommerce, sales
Consolidate all your Sales Teams in real time: stores, ecommerce, sales
teams. Get real time control of the inventory and accurate forecasts to manage
procurements.
@@ -112,7 +112,7 @@
<h3 class="oe_slogan">Odoo Point of Sale will facilitate your inventory management</h3>
<div class="text-center">
<p class="oe_mt32">
Consolidate all your sales channels in real time: stores, e-commerce, inside sales. Get real time control of the inventory and accurate forecasts to manage procurements.
Consolidate all your Sales Teams in real time: stores, e-commerce, inside sales. Get real time control of the inventory and accurate forecasts to manage procurements.
</p>
<p class="oe_mt16">
Odoo POS is fully integrated with other Odoo apps. Odoo Inventory app automatically inputs any transaction from the POS in the stock. You can see in real time the availability of products without losing time. Furthermore, the POS is compatible with Odoo e-commerce. You won't need to setup separate stocks for the two apps and be a truly multi-channel business, without headache.
+1 -1
View File
@@ -10,7 +10,7 @@
'summary': 'Link module between Point of Sale and Sales',
'description': """
This module adds a custom Sales Channel for the point of sale to be able to view and manage your point of sale sales with more ease.
This module adds a custom Sales Team for the point of sale to be able to view and manage your point of sale sales with more ease.
""",
'depends': ['point_of_sale', 'sale_management'],
'data': [
+2 -2
View File
@@ -8,6 +8,6 @@ class PosConfig(models.Model):
_inherit = 'pos.config'
crm_team_id = fields.Many2one(
'crm.team', string="Sales Channel", domain=[('team_type', '=', 'pos')],
'crm.team', string="Sales Team", domain=[('team_type', '=', 'pos')],
default=lambda self: self.env['crm.team'].search([('team_type', '=', 'pos')], limit=1).id,
help="This Point of sale's sales will be related to this Sales Channel.")
help="This Point of sale's sales will be related to this Sales Team.")
+1 -1
View File
@@ -7,4 +7,4 @@ from odoo import fields, models
class PosSession(models.Model):
_inherit = 'pos.session'
crm_team_id = fields.Many2one('crm.team', related='config_id.crm_team_id', string="Sales Channel", readonly=True)
crm_team_id = fields.Many2one('crm.team', related='config_id.crm_team_id', string="Sales Team", readonly=True)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="pos_sale_rule_pos_channel_pos_manager" model="ir.rule">
<field name="name">POS Sales Channel</field>
<field name="name">POS Sales Team</field>
<field name="model_id" ref="sales_team.model_crm_team"/>
<field name="domain_force">[('team_type', '=', 'pos')]</field>
<field name="groups" eval="[(4, ref('point_of_sale.group_pos_manager'))]"/>
+2 -2
View File
@@ -87,7 +87,7 @@ Opportunities can be converted into quotations in just one click.
Drive Engagement with Gamification
----------------------------------
Align sales channels on clear targets. Define clear commission plans. Get real
Align Sales Teams on clear targets. Define clear commission plans. Get real
time statistics on the performance of individual sales or channels. Motivate your
teams with challenges, rewards and leaderboards.
@@ -96,5 +96,5 @@ Have Clear Pricing Strategies
Use pricelists to record special conditions for a specific customer or to
define prices for a segment of customers. Define promotions and have them
applied automatically for all your sales channels.
applied automatically for all your Sales Teams.
+2
View File
@@ -27,6 +27,7 @@
<field name="name">Quotation sent</field>
<field name="sequence">20</field>
<field name="res_model">crm.team</field>
<field name="default" eval="True"/>
<field name="parent_id" eval="ref('sale.mt_order_sent')"/>
<field name="relation_field">team_id</field>
</record>
@@ -34,6 +35,7 @@
<field name="name">Sales Order Confirmed</field>
<field name="sequence">21</field>
<field name="res_model">crm.team</field>
<field name="default" eval="True"/>
<field name="parent_id" eval="ref('sale.mt_order_confirmed')"/>
<field name="relation_field">team_id</field>
</record>
+6
View File
@@ -688,5 +688,11 @@ Thanks!</field>
<!-- Confirm some Sales Orders-->
<function model="sale.order" name="action_confirm" eval="[[ref('portal_sale_order_2')]]"/>
<function model="crm.team" name="message_unsubscribe"
eval="[ref('sales_team.team_sales_department'),ref('sales_team.salesteam_website_sales')], [ref('base.partner_root')]"/>
<function model="crm.team" name="message_subscribe"
eval="[ref('sales_team.team_sales_department'),ref('sales_team.salesteam_website_sales')], [ref('base.partner_root')]"/>
</data>
</odoo>
+1 -1
View File
@@ -16,7 +16,7 @@ class AccountInvoice(models.Model):
if invoice_type == 'out_invoice' and self.env['ir.config_parameter'].sudo().get_param('sale.use_sale_note'):
return self.env.user.company_id.sale_note
team_id = fields.Many2one('crm.team', string='Sales Channel', default=_get_default_team, oldname='section_id')
team_id = fields.Many2one('crm.team', string='Sales Team', default=_get_default_team, oldname='section_id')
comment = fields.Text(default=_default_comment)
partner_shipping_id = fields.Many2one(
'res.partner',
+6 -6
View File
@@ -122,15 +122,15 @@ class SaleOrder(models.Model):
('sale', 'Sales Order'),
('done', 'Locked'),
('cancel', 'Cancelled'),
], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft')
], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', track_sequence=3, default='draft')
date_order = fields.Datetime(string='Order Date', required=True, readonly=True, index=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, copy=False, default=fields.Datetime.now)
validity_date = fields.Date(string='Quote Validity', readonly=True, copy=False, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
help="Validity date of the quotation, after this date, the customer won't be able to validate the quotation online.")
is_expired = fields.Boolean(compute='_compute_is_expired', string="Is expired")
create_date = fields.Datetime(string='Creation Date', readonly=True, index=True, help="Date on which sales order is created.")
confirmation_date = fields.Datetime(string='Confirmation Date', readonly=True, index=True, help="Date on which the sales order is confirmed.", oldname="date_confirm", copy=False)
user_id = fields.Many2one('res.users', string='Salesperson', index=True, track_visibility='onchange', default=lambda self: self.env.user)
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, required=True, change_default=True, index=True, track_visibility='always')
user_id = fields.Many2one('res.users', string='Salesperson', index=True, track_visibility='onchange', track_sequence=2, default=lambda self: self.env.user)
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, required=True, change_default=True, index=True, track_visibility='always', track_sequence=1)
partner_invoice_id = fields.Many2one('res.partner', string='Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, help="Invoice address for current sales order.")
partner_shipping_id = fields.Many2one('res.partner', string='Delivery Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, help="Delivery address for current sales order.")
@@ -151,14 +151,14 @@ class SaleOrder(models.Model):
note = fields.Text('Terms and conditions', default=_default_note)
amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='onchange')
amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='onchange', track_sequence=5)
amount_tax = fields.Monetary(string='Taxes', store=True, readonly=True, compute='_amount_all')
amount_total = fields.Monetary(string='Total', store=True, readonly=True, compute='_amount_all', track_visibility='always')
amount_total = fields.Monetary(string='Total', store=True, readonly=True, compute='_amount_all', track_visibility='always', track_sequence=6)
payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms', oldname='payment_term')
fiscal_position_id = fields.Many2one('account.fiscal.position', oldname='fiscal_position', string='Fiscal Position')
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('sale.order'))
team_id = fields.Many2one('crm.team', 'Sales Channel', change_default=True, default=_get_default_team, oldname='section_id')
team_id = fields.Many2one('crm.team', 'Sales Team', change_default=True, default=_get_default_team, oldname='section_id')
signature = fields.Binary('Signature', help='Signature received through the portal.', copy=False, attachment=True)
signed_by = fields.Char('Signed by', help='Name of the person that signed the SO.', copy=False)
+1 -1
View File
@@ -11,7 +11,7 @@ class CrmTeam(models.Model):
use_quotations = fields.Boolean(string='Quotations', help="Check this box if you send quotations to your customers rather than confirming orders straight away. "
"This will add specific action buttons to your dashboard.")
use_invoices = fields.Boolean('Set Invoicing Target', help="Check this box to set an invoicing target for this sales channel.")
use_invoices = fields.Boolean('Set Invoicing Target', help="Check this box to set an invoicing target for this Sales Team.")
invoiced = fields.Integer(
compute='_compute_invoiced',
string='Invoiced This Month', readonly=True,
+1 -1
View File
@@ -7,7 +7,7 @@ from odoo import fields, models
class AccountInvoiceReport(models.Model):
_inherit = 'account.invoice.report'
team_id = fields.Many2one('crm.team', string='Sales Channel')
team_id = fields.Many2one('crm.team', string='Sales Team')
def _select(self):
return super(AccountInvoiceReport, self)._select() + ", sub.team_id as team_id"
@@ -6,7 +6,7 @@ from odoo import api, fields, models, tools
class PosSaleReport(models.Model):
_name = "report.all.channels.sales"
_description = "All sales orders grouped by sales channels"
_description = "All sales orders grouped by Sales Teams"
_auto = False
name = fields.Char('Order Reference', readonly=True)
@@ -23,7 +23,7 @@ class PosSaleReport(models.Model):
price_subtotal = fields.Float(string='Price Subtotal', readonly=True)
product_qty = fields.Float('Product Quantity', readonly=True)
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True)
team_id = fields.Many2one('crm.team', 'Sales Channel', readonly=True)
team_id = fields.Many2one('crm.team', 'Sales Team', readonly=True)
def _so(self):
so_str = """
@@ -19,7 +19,7 @@
<field name="date_order"/>
<filter string="Date" name="current_year" date="date_order" default_period="this_year"/>
<group expand="1" string="Group By">
<filter string="Sales Channel" name="team_id" context="{'group_by':'team_id'}"/>
<filter string="Sales Team" name="team_id" context="{'group_by':'team_id'}"/>
</group>
</search>
</field>
+1 -1
View File
@@ -33,7 +33,7 @@ class SaleReport(models.Model):
nbr = fields.Integer('# of Lines', readonly=True)
pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', readonly=True)
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True)
team_id = fields.Many2one('crm.team', 'Sales Channel', readonly=True, oldname='section_id')
team_id = fields.Many2one('crm.team', 'Sales Team', readonly=True, oldname='section_id')
country_id = fields.Many2one('res.country', 'Customer Country', readonly=True)
commercial_partner_id = fields.Many2one('res.partner', 'Commercial Entity', readonly=True)
state = fields.Selection([
+1 -1
View File
@@ -43,7 +43,7 @@
</group>
<group expand="1" string="Group By">
<filter string="Salesperson" name="User" context="{'group_by':'user_id'}"/>
<filter string="Sales Channel" name="sales_channel" context="{'group_by':'team_id'}"/>
<filter string="Sales Team" name="sales_channel" context="{'group_by':'team_id'}"/>
<filter string="Customer Country" name="country_id" context="{'group_by':'country_id'}"/>
<filter string="Customer" name="Customer" context="{'group_by':'partner_id'}"/>
<filter string="Product" name="Product" context="{'group_by':'product_id'}"/>
+8 -8
View File
@@ -47,7 +47,7 @@ class TestAccessRights(TestCommonSaleNoChart):
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])]
})
# Create a sales channel
# Create a Sales Team
self.sales_channel = self.env['crm.team'].with_context(tracking_disable=True).create({
'name': 'Test Channel',
})
@@ -80,17 +80,17 @@ class TestAccessRights(TestCommonSaleNoChart):
self.order.sudo(self.user_manager).unlink()
self.assertNotIn(self.order.id, SaleOrder.search([]).ids, 'Sales manager should be able to delete the SO')
# Manager can create a sales channel
# Manager can create a Sales Team
india_channel = self.env['crm.team'].with_context(tracking_disable=True).sudo(self.user_manager).create({
'name': 'India',
})
self.assertIn(india_channel.id, self.env['crm.team'].search([]).ids, 'Sales manager should be able to create a sales channel')
# Manager can edit a sales channel
self.assertIn(india_channel.id, self.env['crm.team'].search([]).ids, 'Sales manager should be able to create a Sales Team')
# Manager can edit a Sales Team
india_channel.sudo(self.user_manager).write({'dashboard_graph_group': 'week'})
self.assertEquals(india_channel.dashboard_graph_group, 'week', 'Sales manager should be able to edit a sales channel')
# Manager can delete a sales channel
self.assertEquals(india_channel.dashboard_graph_group, 'week', 'Sales manager should be able to edit a Sales Team')
# Manager can delete a Sales Team
india_channel.sudo(self.user_manager).unlink()
self.assertNotIn(india_channel.id, self.env['crm.team'].search([]).ids, 'Sales manager should be able to delete a sales channel')
self.assertNotIn(india_channel.id, self.env['crm.team'].search([]).ids, 'Sales manager should be able to delete a Sales Team')
def test_access_sales_person(self):
""" Test Salesperson's access rights """
@@ -100,7 +100,7 @@ class TestAccessRights(TestCommonSaleNoChart):
# Now assign the SO to themselves
self.order.write({'user_id': self.user_salesperson_1.id})
self.order.sudo(self.user_salesperson_1).read()
# Salesperson can change a sales channel of SO
# Salesperson can change a Sales Team of SO
self.order.sudo(self.user_salesperson_1).write({'team_id': self.sales_channel.id})
# Salesperson can't create the SO of other salesperson
with self.assertRaises(AccessError):
+6 -6
View File
@@ -17,7 +17,7 @@
<menuitem id="res_partner_menu"
parent="sale_order_menu"
action="base.action_partner_form"
sequence="3" groups="sales_team.group_sale_salesman"/>
sequence="4" groups="sales_team.group_sale_salesman"/>
<menuitem id="menu_sale_config"
name="Configuration"
@@ -32,7 +32,7 @@
groups= "sales_team.group_sale_manager,account.group_account_manager,account.group_account_user"/>
<menuitem id="sales_team_config"
name="Sales Channels"
name="Sales Teams"
parent="sale.menu_sale_config"
action="sales_team.sales_team_config_action"
sequence="2"/>
@@ -50,8 +50,8 @@
sequence="1"/>
<menuitem id="report_sales_team"
name="Sales Channel Dashboard"
parent="menu_sale_report"
name="Sales Teams"
parent="sale_order_menu"
action="sales_team.crm_team_salesteams_act"
sequence="3"/>
@@ -571,7 +571,7 @@
<field name="name" string="Sales Order" filter_domain="['|','|',('name','ilike',self),('client_order_ref','ilike',self),('partner_id','child_of',self)]"/>
<field name="partner_id" operator="child_of"/>
<field name="user_id"/>
<field name="team_id" string="Sales Channel"/>
<field name="team_id" string="Sales Team"/>
<field name="analytic_account_id"/>
<!-- We only allow to search on the following sale order line fields (product, name) because the other fields, such as price, quantity, ...
will not be searched as often, and if they need to be searched it's usually in the context of products
@@ -878,7 +878,7 @@
<field name="team_id"/>
</xpath>
<xpath expr="//group/filter[@name='status']" position="after">
<filter string="Sales Channel" name="sales_channel" domain="[]" context="{'group_by':'team_id'}"/>
<filter string="Sales Team" name="sales_channel" domain="[]" context="{'group_by':'team_id'}"/>
</xpath>
</field>
</record>
+2 -2
View File
@@ -27,12 +27,12 @@
<field name="inherit_id" ref="account.view_account_invoice_report_search"/>
<field name="arch" type="xml">
<xpath expr="//group/filter[@name='user']" position="after">
<filter string="Sales Channel" name="sales_channel" domain="[]" context="{'group_by':'team_id'}"/>
<filter string="Sales Team" name="sales_channel" domain="[]" context="{'group_by':'team_id'}"/>
</xpath>
</field>
</record>
<!-- Sales Channel Dashboard Views -->
<!-- Sales Team Dashboard Views -->
<record id="action_quotation_form" model="ir.actions.act_window">
<field name="name">New Quotation</field>
<field name="res_model">sale.order</field>
+3 -3
View File
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Sales Channels',
'name': 'Sales Teams',
'version': '1.0',
'category': 'Sales',
'summary': 'Sales Channels',
'summary': 'Sales Teams',
'description': """
Using this application you can manage Sales Channels 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',
+10 -10
View File
@@ -16,7 +16,7 @@ from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
class CrmTeam(models.Model):
_name = "crm.team"
_inherit = ['mail.thread']
_description = "Sales Channel"
_description = "Sales Team"
_order = "name"
@api.model
@@ -40,14 +40,14 @@ class CrmTeam(models.Model):
def _get_default_favorite_user_ids(self):
return [(6, 0, [self.env.uid])]
name = fields.Char('Sales Channel', required=True, translate=True)
active = fields.Boolean(default=True, help="If the active field is set to false, it will allow you to hide the sales channel without removing it.")
name = fields.Char('Sales Team', required=True, translate=True)
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['res.company']._company_default_get('crm.team'))
currency_id = fields.Many2one(
"res.currency", related='company_id.currency_id',
string="Currency", readonly=True)
user_id = fields.Many2one('res.users', string='Channel Leader')
user_id = fields.Many2one('res.users', string='Team Leader')
member_ids = fields.One2many('res.users', 'sale_team_id', string='Channel Members')
favorite_user_ids = fields.Many2many(
'res.users', 'team_favorite_user_rel', 'team_id', 'user_id',
@@ -58,9 +58,9 @@ class CrmTeam(models.Model):
compute='_compute_is_favorite', inverse='_inverse_is_favorite',
help="Favorite teams to display them in the dashboard and access them easily.")
reply_to = fields.Char(string='Reply-To',
help="The email address put in the 'Reply-To' of all emails sent by Odoo about cases in this sales channel")
help="The email address put in the 'Reply-To' of all emails sent by Odoo about cases in this Sales Team")
color = fields.Integer(string='Color Index', help="The color of the channel")
team_type = fields.Selection([('sales', 'Sales'), ('website', 'Website')], string='Channel Type', default='sales', required=True,
team_type = fields.Selection([('sales', 'Sales'), ('website', 'Website')], string='Team Type', default='sales', required=True,
help="The type of this channel, it will define the resources this channel uses.")
dashboard_button_name = fields.Char(string="Dashboard Button", compute='_compute_dashboard_button_name')
dashboard_graph_data = fields.Text(compute='_compute_dashboard_graph')
@@ -143,7 +143,7 @@ class CrmTeam(models.Model):
return 'DATE(%s)' % self._graph_date_column()
def _graph_y_query(self):
raise UserError(_('Undefined graph model for Sales Channel: %s') % self.name)
raise UserError(_('Undefined graph model for Sales Team: %s') % self.name)
def _extra_sql_conditions(self):
return ''
@@ -170,7 +170,7 @@ class CrmTeam(models.Model):
# apply rules
if not self.dashboard_graph_model:
raise UserError(_('Undefined graph model for Sales Channel: %s') % self.name)
raise UserError(_('Undefined graph model for Sales Team: %s') % self.name)
GraphModel = self.env[self.dashboard_graph_model]
graph_table = GraphModel._table
extra_conditions = self._extra_sql_conditions()
@@ -263,14 +263,14 @@ class CrmTeam(models.Model):
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 channel's options
""" 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 channel's options
It will return the adequate action depending on the Sales Team's options
"""
return False
+2 -2
View File
@@ -8,5 +8,5 @@ class ResPartner(models.Model):
_inherit = 'res.partner'
team_id = fields.Many2one(
'crm.team', 'Sales Channel',
help='If set, this sales channel will be used for sales and assignations related to this partner')
'crm.team', 'Sales Team',
help='If set, this Sales Team will be used for sales and assignations related to this partner')
+2 -2
View File
@@ -8,8 +8,8 @@ class ResUsers(models.Model):
_inherit = 'res.users'
sale_team_id = fields.Many2one(
'crm.team', 'Sales Team',
help='Sales Channel the user is member of. Used to compute the members of a sales channel through the inverse one2many')
'crm.team', "User's Sales Team",
help='Sales Team the user is member of. Used to compute the members of a Sales Team through the inverse one2many')
group_sales_team_user = fields.Selection(
selection=lambda self: self._get_group_selection('base.module_category_sales_management'),
@@ -30,7 +30,7 @@
</record>
<record model="ir.rule" id="sale_team_comp_rule">
<field name="name">Sales Channel multi-company</field>
<field name="name">Sales Team multi-company</field>
<field name="model_id" ref="model_crm_team"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
+9 -9
View File
@@ -22,7 +22,7 @@
<!-- Case Teams Action -->
<record id="crm_team_salesteams_act" model="ir.actions.act_window">
<field name="name">Sales Channel Dashboard</field>
<field name="name">Sales Teams</field>
<field name="res_model">crm.team</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,form</field>
@@ -30,9 +30,9 @@
<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 channel
Define a new Sales Team
</p><p>
Use sales channels to organize your sales departments.
Use Sales Teams to organize your sales departments.
Each channel will work with a separate pipeline.
</p>
</field>
@@ -44,7 +44,7 @@
<field name="name">crm.team.form</field>
<field name="model">crm.team</field>
<field name="arch" type="xml">
<form string="Sales Channel">
<form string="Sales Team">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
@@ -52,7 +52,7 @@
</button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Sales Channel"/>
<label for="name" class="oe_edit_only" string="Sales Team"/>
<h1>
<field name="name" placeholder="Salesteam name..."/>
</h1>
@@ -114,7 +114,7 @@
<field name="model">crm.team</field>
<field name="field_parent">child_ids</field>
<field name="arch" type="xml">
<tree string="Sales Channel">
<tree string="Sales Team">
<field name="name"/>
<field name="active" invisible="1"/>
<field name="user_id"/>
@@ -154,7 +154,7 @@
</template>
<record id="sales_team_config_action" model="ir.actions.act_window">
<field name="name">Sales Channels</field>
<field name="name">Sales Teams</field>
<field name="res_model">crm.team</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
@@ -162,9 +162,9 @@
<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 channel
Define a new Sales Team
</p><p>
Use sales channels to organize your sales departments.
Use Sales Teams to organize your sales departments.
Each channel will work with a separate pipeline.
</p>
</field>
+2 -2
View File
@@ -60,8 +60,8 @@ class MailTestFull(models.Model):
count = fields.Integer(default=1)
datetime = fields.Datetime(default=fields.Datetime.now)
mail_template = fields.Many2one('mail.template', 'Template')
customer_id = fields.Many2one('res.partner', 'Customer', track_visibility='onchange')
user_id = fields.Many2one('res.users', 'Responsible', track_visibility='onchange')
customer_id = fields.Many2one('res.partner', 'Customer', track_visibility='onchange', track_sequence=2)
user_id = fields.Many2one('res.users', 'Responsible', track_visibility='onchange', track_sequence=1)
umbrella_id = fields.Many2one('mail.test', track_visibility='onchange')
def _track_template(self, tracking):
@@ -116,3 +116,18 @@ class TestTracking(common.BaseFunctionalTest, common.MockEmails):
self.record.message_ids[1],
[('customer_id', 'many2one', False, self.user_admin.partner_id) # onchange tracked field
])
def test_message_track_sequence(self):
""" Update some tracked fields and check that the mail.tracking.value are ordered according to their track_sequence"""
self.record.write({
'name': 'Zboub',
'customer_id': self.user_admin.partner_id.id,
'user_id': self.user_admin.id,
'umbrella_id': self.env['mail.test'].with_context(mail_create_nosubscribe=True).create({'name': 'Umbrella'}).id
})
self.assertEqual(len(self.record.message_ids), 1, 'should have 1 tracking message')
tracking_values = self.env['mail.tracking.value'].search([('mail_message_id', '=', self.record.message_ids.id)])
self.assertEqual(tracking_values[0].track_sequence, 1)
self.assertEqual(tracking_values[1].track_sequence, 2)
self.assertEqual(tracking_values[2].track_sequence, 100)
+2 -2
View File
@@ -28,10 +28,10 @@ class Website(models.Model):
return [('use_opportunities', '=', True)]
crm_default_team_id = fields.Many2one(
'crm.team', string='Default Sales Channels',
'crm.team', string='Default Sales Teams',
default=lambda self: self.env['crm.team'].search([], limit=1),
domain=lambda self: self._get_crm_default_team_domain(),
help='Default sales channel for new leads created through the Contact Us form.')
help='Default Sales Team for new leads created through the Contact Us form.')
crm_default_user_id = fields.Many2one(
'res.users', string='Default Salesperson', domain=[('share', '=', False)],
help='Default salesperson for new leads created through the Contact Us form.')
@@ -13,9 +13,9 @@ class ResConfigSettings(models.TransientModel):
return [('use_opportunities', '=', True)]
crm_default_team_id = fields.Many2one(
'crm.team', string='Default Sales Channel', related='website_id.crm_default_team_id',
'crm.team', string='Default Sales Team', related='website_id.crm_default_team_id',
domain=lambda self: self._get_crm_default_team_domain(),
help='Default sales channel for new leads created through the Contact Us form.')
help='Default Sales Team for new leads created through the Contact Us form.')
crm_default_user_id = fields.Many2one(
'res.users', string='Default Salesperson', related='website_id.crm_default_user_id', domain=[('share', '=', False)],
help='Default salesperson for new leads created through the Contact Us form.')
@@ -15,9 +15,15 @@
Add a contact form in the <a href="/contactus">Contact Us</a> page
</div>
<div class="row">
<label class="col-md-3 o_light_label" string="Sales Channel" for="crm_default_team_id"/>
<label class="col-md-3 o_light_label" string="Sales Team" for="crm_default_team_id"/>
<field name="crm_default_team_id" class="oe_inline"/>
</div>
<div class="content-group mb16" attrs="{'invisible': [('crm_default_team_id', '!=', False)]}">
<div class="text-muted" id="msg_sale_team_setting">
Please set a Sales Team for the website. Otherwise you can't follow related events.
</div>
</div>
<div class="row">
<label class="col-md-3 o_light_label" string="Salesperson" for="crm_default_user_id"/>
<field name="crm_default_user_id" class="oe_inline"/>
@@ -16,7 +16,7 @@ class CrmLeadReportAssign(models.Model):
grade_id = fields.Many2one('res.partner.grade', 'Grade', readonly=True)
user_id = fields.Many2one('res.users', 'User', readonly=True)
country_id = fields.Many2one('res.country', 'Country', readonly=True)
team_id = fields.Many2one('crm.team', 'Sales Channel', oldname='section_id', readonly=True)
team_id = fields.Many2one('crm.team', 'Sales Team', oldname='section_id', readonly=True)
company_id = fields.Many2one('res.company', 'Company', readonly=True)
date_assign = fields.Date('Assign Date', readonly=True)
create_date = fields.Datetime('Create Date', readonly=True)
@@ -21,7 +21,7 @@
<group expand="1" string="Group By">
<filter string="Salesperson" name="user"
context="{'group_by':'user_id'}" />
<filter string="Sales Channel" name="sales_channel" context="{'group_by':'team_id'}"/>
<filter string="Sales Team" name="sales_channel" context="{'group_by':'team_id'}"/>
<filter string="Country" name="country" context="{'group_by':'country_id'}" />
<filter string="Company" name="company" context="{'group_by':'company_id'}" />
<separator/>
@@ -18,7 +18,7 @@ class CrmPartnerReportAssign(models.Model):
date_review = fields.Date('Latest Partner Review')
date_partnership = fields.Date('Partnership Date')
country_id = fields.Many2one('res.country', 'Country', readonly=True)
team_id = fields.Many2one('crm.team', 'Sales Channel', oldname='section_id', readonly=True)
team_id = fields.Many2one('crm.team', 'Sales Team', oldname='section_id', readonly=True)
nbr_opportunities = fields.Integer('# of Opportunity', readonly=True, oldname='opp')
turnover = fields.Float('Turnover', readonly=True)
date = fields.Date('Invoice Account Date', readonly=True)
@@ -14,7 +14,7 @@
<group expand="1" string="Group By">
<filter string="Salesperson" name="user"
context="{'group_by':'user_id'}" />
<filter string="Sales Channel" name="sales_team"
<filter string="Sales Team" name="sales_team"
context="{'group_by':'team_id'}"/>
<filter string="Partner" name="partner"
context="{'group_by':'partner_id'}" />
@@ -388,7 +388,7 @@
<span class="col-xs-8" t-field="lead.user_id" />
</div>
<div class="row" t-if="lead.team_id">
<label class="col-xs-4">Sales Channel</label>
<label class="col-xs-4">Sales Team</label>
<span class="col-xs-8" t-field="lead.team_id" />
</div>
</div>
+1 -1
View File
@@ -10,7 +10,7 @@ from odoo import fields, models, _
class CrmTeam(models.Model):
_inherit = "crm.team"
website_ids = fields.One2many('website', 'salesteam_id', string='Websites', help="Websites using this sales channel")
website_ids = fields.One2many('website', 'salesteam_id', string='Websites', help="Websites using this Sales Team")
abandoned_carts_count = fields.Integer(
compute='_compute_abandoned_carts',
string='Number of Abandoned Carts', readonly=True)
@@ -15,7 +15,7 @@ class ResConfigSettings(models.TransientModel):
return False
salesperson_id = fields.Many2one('res.users', related='website_id.salesperson_id', string='Salesperson')
salesteam_id = fields.Many2one('crm.team', related='website_id.salesteam_id', string='Sales Channel', domain=[('team_type', '!=', 'pos')])
salesteam_id = fields.Many2one('crm.team', related='website_id.salesteam_id', string='Sales Team', domain=[('team_type', '!=', 'pos')])
module_website_sale_delivery = fields.Boolean("eCommerce Shipping Costs")
# field used to have a nice radio in form view, resuming the 2 fields above
sale_delivery_settings = fields.Selection([
+1 -1
View File
@@ -16,7 +16,7 @@ class Website(models.Model):
pricelist_id = fields.Many2one('product.pricelist', compute='_compute_pricelist_id', string='Default Pricelist')
currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', related_sudo=False, string='Default Currency')
salesperson_id = fields.Many2one('res.users', string='Salesperson')
salesteam_id = fields.Many2one('crm.team', string='Sales Channel')
salesteam_id = fields.Many2one('crm.team', string='Sales Team')
pricelist_ids = fields.One2many('product.pricelist', compute="_compute_pricelist_ids",
string='Price list available for this Ecommerce/Website')
@@ -369,7 +369,7 @@
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box" id="checkout_assignation_setting" groups="base.group_no_one">
<div class="col-xs-12 col-md-6 o_setting_box" id="checkout_assignation_setting">
<div class="o_setting_right_pane">
<span class="o_form_label">Assignation</span>
<div class="text-muted">
@@ -377,7 +377,7 @@
</div>
<div class="content-group">
<div class="row mt16">
<label class="o_light_label col-md-3" string="Sales Channel" for="salesteam_id"/>
<label class="o_light_label col-md-3" string="Sales Team" for="salesteam_id"/>
<field name="salesteam_id" context="{'default_team_type': 'website'}"/>
</div>
<div class="row">
+1 -1
View File
@@ -551,7 +551,7 @@ Alias support integration
Aliases are usually configured on a parent model which will then create specific
record when contacted by e-mail. For example, Project have aliases to create tasks
or issues, Sales Channel have aliases to generate Leads.
or issues, Sales Team have aliases to generate Leads.
.. note:: The model that will be created by the alias **must** inherit the
``mail_thread`` model.