[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:
@@ -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>
|
||||
|
||||
@@ -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'}"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user