[IMP] mass_mailing, *: improve mailing stat buttons

*mass_mailing_crm, mass_mailing_sale 

Currently, the Mailing stat buttons have a few problems.
The business reporting smart buttons send the user to a list of linked records,
which doesn't provide much value (e.g. what is the user supposed to do with a
list of 12345 leads?). They also show wrong values depending on access rights.
The Mailing KPI smart buttons lead to a blank screen if there's no data to show.

In this commit, we add/adapt all action helpers to help users understand what is
going on. For the business reporting smart buttons we instead send users to
relevant reporting actions, so they can make sense of the data we showed before.
The values of the business reporting smart buttons are now calculated as a super
user.
We also change some domains from the whole UTM Sale Domain to only using
Source ID, which simplifies computation while staying accurate.

task-2604515

closes odoo/odoo#76798

Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
This commit is contained in:
Philémon van Helden
2021-11-30 16:53:33 +00:00
parent 89fbe1c34e
commit 3b833c73a7
8 changed files with 127 additions and 69 deletions
@@ -117,7 +117,10 @@
<filter string="Status" name="status" context="{'group_by':'state'}"/>
<filter string="Company" name="company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<separator orientation="vertical" />
<filter string="Date" name="invoice_date" context="{'group_by':'invoice_date'}"/>
<filter string="Date" name="invoice_date" context="{'group_by':'invoice_date'}"
invisible="context.get('invoice_report_view_hide_invoice_date')"/>
<filter string="Date" name="group_by_invoice_date_week" context="{'group_by':'invoice_date:week'}"
invisible="not context.get('invoice_report_view_hide_invoice_date')"/>
<filter string="Due Date" name="duemonth" context="{'group_by':'invoice_date_due:month'}"/>
</group>
</search>
+4 -1
View File
@@ -998,7 +998,10 @@
<filter string="Medium" name="medium" domain="[]" context="{'group_by':'medium_id'}"/>
<filter string="Source" name="source" domain="[]" context="{'group_by':'source_id'}"/>
<separator orientation="vertical" />
<filter string="Creation Date" context="{'group_by':'create_date:month'}" name="month"/>
<filter string="Creation Date" name="month" context="{'group_by':'create_date:month'}"
invisible="context.get('crm_lead_view_hide_month')"/>
<filter string="Creation Date" name="group_by_create_date_day" context="{'group_by':'create_date:day'}"
invisible="not context.get('crm_lead_view_hide_month')"/>
<filter string="Conversion Date" name="date_conversion" context="{'group_by': 'date_conversion'}" groups="crm.group_use_lead"/>
<filter string="Expected Closing" name="date_deadline" context="{'group_by':'date_deadline'}"/>
<filter string="Closed Date" name="date_closed" context="{'group_by':'date_closed'}"/>
+31 -6
View File
@@ -11,6 +11,7 @@ import threading
import werkzeug.urls
from ast import literal_eval
from dateutil.relativedelta import relativedelta
from markupsafe import Markup
from werkzeug.urls import url_join
from odoo import api, fields, models, tools, _
@@ -544,12 +545,19 @@ class MassMailing(models.Model):
def action_view_clicked(self):
model_name = self.env['ir.model']._get('link.tracker').display_name
recipient = self.env['ir.model']._get(self.mailing_model_real).display_name
helper_header = _("No %s clicked your mailing yet!", recipient)
helper_message = _("Link Trackers will measure how many times each link is clicked as well as "
"the proportion of %s who clicked at least once in your mailing.", recipient)
return {
'name': model_name,
'type': 'ir.actions.act_window',
'view_mode': 'tree',
'res_model': 'link.tracker',
'domain': [('mass_mailing_id.id', '=', self.id)],
'help': Markup('<p class="o_view_nocontent_smiling_face">%s</p><p>%s</p>') % (
helper_header, helper_message,
),
'context': dict(self._context, create=False)
}
@@ -566,28 +574,45 @@ class MassMailing(models.Model):
return self._action_view_documents_filtered('delivered')
def _action_view_documents_filtered(self, view_filter):
if view_filter in ('reply', 'bounce'):
model_name = self.env['ir.model']._get(self.mailing_model_real).display_name
helper_header = None
helper_message = None
if view_filter == 'reply':
found_traces = self.mailing_trace_ids.filtered(lambda trace: trace.trace_status == view_filter)
helper_header = _("No %s replied to your mailing yet!", model_name)
helper_message = _("To track how many replies this mailing gets, make sure "
"its reply-to address belongs to this database.")
elif view_filter == 'bounce':
found_traces = self.mailing_trace_ids.filtered(lambda trace: trace.trace_status == view_filter)
helper_header = _("No %s address bounced yet!", model_name)
helper_message = _("Bounce happens when a mailing cannot be delivered (fake address, "
"server issues, ...). Check each record to see what went wrong.")
elif view_filter == 'open':
found_traces = self.mailing_trace_ids.filtered(lambda trace: trace.trace_status in ('open', 'reply'))
elif view_filter == 'click':
found_traces = self.mailing_trace_ids.filtered(lambda trace: trace.links_click_datetime)
helper_header = _("No %s opened your mailing yet!", model_name)
helper_message = _("Come back once your mailing has been sent to track who opened your mailing.")
elif view_filter == 'delivered':
found_traces = self.mailing_trace_ids.filtered(lambda trace: trace.trace_status in ('sent', 'open', 'reply'))
helper_header = _("No %s received your mailing yet!", model_name)
helper_message = _("Wait until your mailing has been sent to check how many recipients you managed to reach.")
elif view_filter == 'sent':
found_traces = self.mailing_trace_ids.filtered(lambda trace: trace.sent_datetime)
else:
found_traces = self.env['mailing.trace']
res_ids = found_traces.mapped('res_id')
model_name = self.env['ir.model']._get(self.mailing_model_real).display_name
return {
action = {
'name': model_name,
'type': 'ir.actions.act_window',
'view_mode': 'tree',
'res_model': self.mailing_model_real,
'domain': [('id', 'in', res_ids)],
'context': dict(self._context, create=False)
'context': dict(self._context, create=False),
}
if helper_header and helper_message:
action['help'] = Markup('<p class="o_view_nocontent_smiling_face">%s</p><p>%s</p>') % (
helper_header, helper_message,
),
return action
def update_opt_out(self, email, list_ids, value):
if len(list_ids) > 0:
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from markupsafe import Markup
from odoo import fields, models, _, tools
@@ -9,27 +10,40 @@ class MassMailing(models.Model):
_inherit = 'mailing.mailing'
use_leads = fields.Boolean('Use Leads', compute='_compute_use_leads')
crm_lead_count = fields.Integer('Leads/Opportunities Count', groups='sales_team.group_sale_salesman', compute='_compute_crm_lead_count')
crm_lead_count = fields.Integer('Leads/Opportunities Count', compute='_compute_crm_lead_count')
def _compute_use_leads(self):
self.use_leads = self.env.user.has_group('crm.group_use_lead')
def _compute_crm_lead_count(self):
lead_data = self.env['crm.lead'].with_context(active_test=False).read_group(
lead_data = self.env['crm.lead'].with_context(active_test=False).sudo().read_group(
[('source_id', 'in', self.source_id.ids)],
['source_id'], ['source_id']
['source_id'], ['source_id'],
)
mapped_data = {datum['source_id'][0]: datum['source_id_count'] for datum in lead_data}
for mass_mailing in self:
mass_mailing.crm_lead_count = mapped_data.get(mass_mailing.source_id.id, 0)
def action_redirect_to_leads_and_opportunities(self):
view = 'crm.crm_lead_all_leads' if self.use_leads else 'crm.crm_lead_opportunities'
action = self.env.ref(view).sudo().read()[0]
action['view_mode'] = 'tree,kanban,graph,pivot,form,calendar'
action['domain'] = [('source_id', 'in', self.source_id.ids)]
action['context'] = {'active_test': False, 'create': False}
return action
text = _("Leads") if self.use_leads else _("Opportunities")
helper_header = _("No %s yet!", text)
helper_message = _("Note that Odoo cannot track replies if they are sent towards email addresses to this database.")
return {
'context': {
'active_test': False,
'create': False,
'search_default_group_by_create_date_day': True,
'crm_lead_view_hide_month': True,
},
'domain': [('source_id', 'in', self.source_id.ids)],
'help': Markup('<p class="o_view_nocontent_smiling_face">%s</p><p>%s</p>') % (
helper_header, helper_message,
),
'name': _("Leads Analysis"),
'res_model': 'crm.lead',
'type': 'ir.actions.act_window',
'view_mode': 'graph,pivot,tree,form',
}
def _prepare_statistics_email_values(self):
self.ensure_one()
@@ -10,7 +10,6 @@
type="object"
icon="fa-star"
class="oe_stat_button"
groups="sales_team.group_sale_salesman"
attrs="{'invisible': [('state', '=', 'draft')]}">
<div class="o_field_widget o_stat_info">
<field name="use_leads" invisible="1"/>
@@ -22,4 +21,4 @@
</xpath>
</field>
</record>
</odoo>
</odoo>
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from markupsafe import Markup
from odoo import api, fields, models, _, tools
from odoo.osv import expression
@@ -10,64 +10,77 @@ class MassMailing(models.Model):
_name = 'mailing.mailing'
_inherit = 'mailing.mailing'
sale_quotation_count = fields.Integer('Quotation Count', groups='sales_team.group_sale_salesman', compute='_compute_sale_quotation_count')
sale_invoiced_amount = fields.Integer('Invoiced Amount', groups='sales_team.group_sale_salesman', compute='_compute_sale_invoiced_amount')
sale_quotation_count = fields.Integer('Quotation Count', compute='_compute_sale_quotation_count')
sale_invoiced_amount = fields.Integer('Invoiced Amount', compute='_compute_sale_invoiced_amount')
@api.depends('mailing_domain')
def _compute_sale_quotation_count(self):
has_so_access = self.env['sale.order'].check_access_rights('read', raise_exception=False)
if not has_so_access:
self.sale_quotation_count = 0
return
quotation_data = self.env['sale.order'].sudo().read_group(
[('source_id', 'in', self.source_id.ids)],
['source_id'], ['source_id'],
)
mapped_data = {datum['source_id'][0]: datum['source_id_count'] for datum in quotation_data}
for mass_mailing in self:
mass_mailing.sale_quotation_count = self.env['sale.order'].search_count(mass_mailing._get_sale_utm_domain())
mass_mailing.sale_quotation_count = mapped_data.get(mass_mailing.source_id.id, 0)
@api.depends('mailing_domain')
def _compute_sale_invoiced_amount(self):
if not self.user_has_groups('sales_team.group_sale_salesman') or not self.user_has_groups('account.group_account_invoice'):
self.sale_invoiced_amount = 0
return
domain = expression.AND([
[('source_id', 'in', self.source_id.ids)],
[('state', 'not in', ['draft', 'cancel'])]
])
moves_data = self.env['account.move'].sudo().read_group(
domain, ['source_id', 'amount_untaxed_signed'], ['source_id'],
)
mapped_data = {datum['source_id'][0]: datum['amount_untaxed_signed'] for datum in moves_data}
for mass_mailing in self:
domain = expression.AND([
mass_mailing._get_sale_utm_domain(),
[('state', 'not in', ['draft', 'cancel'])]
])
moves = self.env['account.move'].search_read(domain, ['amount_untaxed_signed'])
mass_mailing.sale_invoiced_amount = sum(i['amount_untaxed_signed'] for i in moves)
mass_mailing.sale_invoiced_amount = mapped_data.get(mass_mailing.source_id.id, 0)
def action_redirect_to_quotations(self):
action = self.env["ir.actions.actions"]._for_xml_id("sale.action_quotations_with_onboarding")
action['domain'] = self._get_sale_utm_domain()
action['context'] = {'create': False}
return action
helper_header = _("No Quotations yet!")
helper_message = _("Quotations will appear here once your customers add "
"products to their Carts or when your sales reps assign this mailing.")
return {
'context': {
'create': False,
'search_default_group_by_date_day': True,
'sale_report_view_hide_date': True,
},
'domain': [('source_id', '=', self.source_id.id)],
'help': Markup('<p class="o_view_nocontent_smiling_face">%s</p><p>%s</p>') % (
helper_header, helper_message,
),
'name': _("Sales Analysis"),
'res_model': 'sale.report',
'type': 'ir.actions.act_window',
'view_mode': 'graph,pivot,tree,form',
}
def action_redirect_to_invoiced(self):
action = self.env["ir.actions.actions"]._for_xml_id("account.action_move_out_invoice_type")
moves = self.env['account.move'].search(self._get_sale_utm_domain())
action['context'] = {
'create': False,
'edit': False,
'view_no_maturity': True
domain = expression.AND([
[('source_id', '=', self.source_id.id)],
[('state', 'not in', ['draft', 'cancel'])]
])
moves = self.env['account.move'].search(domain)
helper_header = _("No Revenues yet!")
helper_message = _("Revenues will appear here once orders are turned into invoices.")
return {
'context': {
'create': False,
'edit': False,
'view_no_maturity': True,
'search_default_group_by_invoice_date_week': True,
'invoice_report_view_hide_invoice_date': True,
},
'domain': [('move_id', 'in', moves.ids)],
'help': Markup('<p class="o_view_nocontent_smiling_face">%s</p><p>%s</p>') % (
helper_header, helper_message,
),
'name': _("Invoices Analysis"),
'res_model': 'account.invoice.report',
'type': 'ir.actions.act_window',
'view_mode': 'graph,pivot,tree,form',
}
action['domain'] = [
('id', 'in', moves.ids),
('move_type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt')),
('state', 'not in', ['draft', 'cancel'])
]
action['context'] = {'create': False}
return action
def _get_sale_utm_domain(self):
res = []
if self.campaign_id:
res.append(('campaign_id', '=', self.campaign_id.id))
if self.source_id:
res.append(('source_id', '=', self.source_id.id))
if self.medium_id:
res.append(('medium_id', '=', self.medium_id.id))
if not res:
res.append((0, '=', 1))
return res
def _prepare_statistics_email_values(self):
self.ensure_one()
@@ -10,7 +10,6 @@
type="object"
icon="fa-pencil-square-o"
class="oe_stat_button"
groups="sales_team.group_sale_salesman"
attrs="{'invisible': [('state', '=', 'draft')]}">
<field name="sale_quotation_count" string="Quotations" widget="statinfo"/>
</button>
@@ -18,11 +17,10 @@
type="object"
icon="fa-dollar"
class="oe_stat_button"
groups="sales_team.group_sale_salesman"
attrs="{'invisible': [('state', '=', 'draft')]}" >
<field name="sale_invoiced_amount" string="Invoiced" widget="statinfo"/>
</button>
</xpath>
</field>
</record>
</odoo>
</odoo>
+4 -1
View File
@@ -76,7 +76,10 @@
<filter name="status" string="Status" context="{'group_by':'state'}"/>
<filter string="Company" name="company" groups="base.group_multi_company" context="{'group_by':'company_id'}"/>
<separator/>
<filter string="Order Date" name="date" context="{'group_by':'date'}"/>
<filter string="Order Date" name="date" context="{'group_by':'date'}"
invisible="context.get('sale_report_view_hide_date')"/>
<filter string="Order Date" name="group_by_date_day" context="{'group_by':'date:day'}"
invisible="not context.get('sale_report_view_hide_date')"/>
</group>
</search>
</field>