[MERGE] mail, sms: improve composers views

This merge will improve the layout of the mail composer and the resend modals

  * revamp the mail composer and the resend modals: It will update the labels,
    clarify the helper messages and make the buttons more dynamic;
  * simplify the mail composer: advanced options will be placed in a dedicated
    pane;
  * various minor layout adjustments;

See commit messages for more info.

Task-2523036

closes odoo/odoo#71413

Related: odoo/upgrade#2877
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2021-09-30 15:31:54 +02:00
committed by GitHub
11 changed files with 112 additions and 85 deletions
@@ -115,6 +115,7 @@ function factory(dependencies) {
target: 'current',
res_model: this.res_model,
domain: [['message_has_error', '=', true]],
context: { create: false },
},
});
if (this.messaging.device.isMobile) {
+3 -3
View File
@@ -34,9 +34,9 @@
</div>
<div class="oe_title">
<label for="name"/>
<h1><field name="name" required="1" placeholder="e.g. Calendar: Reminder"/></h1>
<h1><field name="name" required="1" placeholder='e.g. "Welcome email"'/></h1>
<group>
<field name="model_id" placeholder="e.g. Users" required="1" options="{'no_create': True}"/>
<field name="model_id" placeholder="e.g. Contact" required="1" options="{'no_create': True}"/>
<field name="model" invisible="1"/>
</group>
</div>
@@ -44,7 +44,7 @@
<page string="Content" name="content">
<div class="oe_title"><label for="subject"/></div>
<div class="oe_title">
<h2 style="display: inline-block;"><field name="subject" placeholder="Subject (placeholders may be used here)"/></h2>
<h2 style="display: inline-block;"><field name="subject" placeholder='e.g. "Welcome to MyCompany" or "Nice to meet you, ${object.name}"'/></h2>
</div>
<field name="body_html" widget="html" options="{'style-inline': true, 'codeview': true }"/>
<field name="attachment_ids" widget="many2many_binary"/>
+2 -2
View File
@@ -130,8 +130,8 @@ class MailComposer(models.TransientModel):
string='Considers answers as new thread',
help='Manage answers as new incoming emails instead of replies going to the same thread.')
reply_to_mode = fields.Selection([
('update', 'Log in the original discussion thread'),
('new', 'Redirect to another email address')],
('update', 'Store email and replies in the chatter of each record'),
('new', 'Collect replies on a specific email address')],
string='Replies', compute='_compute_reply_to_mode', inverse='_inverse_reply_to_mode',
help="Original Discussion: Answers go in the original document discussion thread. \n Another Email Address: Answers go to the email address mentioned in the tracking message-id instead of original document discussion thread. \n This has an impact on the generated message-id.")
is_log = fields.Boolean('Log an Internal Note',
@@ -6,7 +6,7 @@
<field name="model">mail.compose.message</field>
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
<field name="arch" type="xml">
<form string="Compose Email">
<form string="Compose Email" class="pt-0 pb-0">
<group>
<!-- truly invisible fields for control and options -->
<field name="composition_mode" invisible="1"/>
@@ -15,66 +15,52 @@
<field name="is_log" invisible="1"/>
<field name="parent_id" invisible="1"/>
<field name="mail_server_id" invisible="1"/>
<field name="active_domain" invisible="1"/>
<!-- Various warnings -->
<div colspan="2" class="oe_form_box_info bg-info oe_text_center"
attrs="{'invisible': [('active_domain', '=', False)]}">
<p attrs="{'invisible': [('use_active_domain', '=', False)]}">
<strong>
All records matching your current search filter will be mailed,
not only the ids selected in the list view.
</strong><br />
The email will be sent for all the records selected in the list.<br />
Confirming this wizard will probably take a few minutes blocking your browser.
</p>
<p attrs="{'invisible': [('use_active_domain', '=', True)]}">
<strong>Only records checked in list view will be used.</strong><br />
The email will be sent for all the records selected in the list.
</p>
<p class="mt8">
<span attrs="{'invisible': [('use_active_domain', '=', True)]}">
If you want to send it for all the records matching your search criterion, check this box :
</span>
<span attrs="{'invisible': [('use_active_domain', '=', False)]}">
If you want to use only selected records please uncheck this selection box :
</span>
<field class="oe_inline" name="use_active_domain"/>
</p>
</div>
<!-- visible wizard -->
<field name="email_from"
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
<label for="partner_ids" string="Recipients" attrs="{'invisible': [('is_log', '=', True)]}" groups="base.group_user"/>
<label for="partner_ids" string="Recipients" attrs="{'invisible': ['|', ('is_log', '=', True), ('composition_mode', '!=', 'comment')]}"/>
<div groups="base.group_user" attrs="{'invisible': [('is_log', '=', True)]}">
<span attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}">
<strong>Email mass mailing</strong> on
<span attrs="{'invisible': [('use_active_domain', '=', True)]}">the selected records</span>
<span attrs="{'invisible': [('use_active_domain', '=', False)]}">the current search filter</span>.
</span>
<span name="document_followers_text" attrs="{'invisible':['|', ('model', '=', False), ('composition_mode', '=', 'mass_mail')]}">Followers of the document and</span>
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
context="{'force_email':True, 'show_email':True}"
attrs="{'invisible': [('composition_mode', '!=', 'comment')]}"/>
</div>
<field name="subject" placeholder="Subject..." required="True"/>
<field name="subject" placeholder="Welcome to MyCompany!" required="True"/>
<!-- mass post -->
<field name="notify"
attrs="{'invisible':[('composition_mode', '!=', 'mass_post')]}"/>
<!-- mass mailing -->
<field name="reply_to_force_new" invisible="1"/>
<field name="reply_to_mode" attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}" widget="radio"/>
<field name="reply_to" placeholder="Email address to which replies will be redirected"
attrs="{'invisible':['|', ('reply_to_mode', '=', 'update'), ('composition_mode', '!=', 'mass_mail')],
'required':[('reply_to_mode', '!=', 'update'), ('composition_mode', '=', 'mass_mail')]}"/>
</group>
<field name="can_edit_body" invisible="1"/>
<field name="body" options="{'style-inline': true}" attrs="{'readonly': [('can_edit_body', '=', False)]}" force_save="1"/>
<group col="4">
<field name="attachment_ids" widget="many2many_binary" string="Attach a file" nolabel="1" colspan="2"/>
<field name="template_id" options="{'no_create': True}"
context="{'default_model': model, 'default_body_html': body, 'default_subject': subject}"/>
</group>
<div attrs="{'invisible': [('composition_mode', '=', 'mass_mail')]}">
<field name="body" placeholder="Write your message here..." options="{'style-inline': true}" attrs="{'readonly': [('can_edit_body', '=', False)]}" force_save="1"/>
<group col="4">
<field name="attachment_ids" widget="many2many_binary" string="Attach a file" nolabel="1" colspan="2"/>
<field name="template_id" string="Load template" options="{'no_create': True}"
context="{'default_model': model, 'default_body_html': body, 'default_subject': subject}"/>
</group>
</div>
<notebook attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}">
<page string="Content">
<div>
<field name="body" placeholder="Write your message here..." options="{'style-inline': true}" attrs="{'readonly': [('can_edit_body', '=', False)]}" force_save="1"/>
<group col="4">
<field name="attachment_ids" widget="many2many_binary" string="Attach a file" nolabel="1" colspan="2"/>
<field name="template_id" string="Load template" options="{'no_create': True}"
context="{'default_model': model, 'default_body_html': body, 'default_subject': subject}"/>
</group>
</div>
</page>
<page string="Settings">
<!-- mass mailing -->
<field name="reply_to_force_new" invisible="1"/>
<field name="reply_to_mode" attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}" widget="radio"/>
<group>
<field name="reply_to" string="Reply-to Address" placeholder='e.g: "info@mycompany.odoo.com"'
attrs="{'invisible':['|', ('reply_to_mode', '=', 'update'), ('composition_mode', '!=', 'mass_mail')],
'required':[('reply_to_mode', '!=', 'update'), ('composition_mode', '=', 'mass_mail')]}"/>
</group>
</page>
</notebook>
<footer>
<button string="Send" attrs="{'invisible': [('is_log', '=', True)]}" name="action_send_mail" type="object" class="btn-primary o_mail_send" data-hotkey="q"/>
<button string="Log" attrs="{'invisible': [('is_log', '=', False)]}" name="action_send_mail" type="object" class="btn-primary" data-hotkey="q"/>
+12 -7
View File
@@ -12,12 +12,17 @@ class MailResendMessage(models.TransientModel):
mail_message_id = fields.Many2one('mail.message', 'Message', readonly=True)
partner_ids = fields.One2many('mail.resend.partner', 'resend_wizard_id', string='Recipients')
notification_ids = fields.Many2many('mail.notification', string='Notifications', readonly=True)
has_cancel = fields.Boolean(compute='_compute_has_cancel')
can_cancel = fields.Boolean(compute='_compute_can_cancel')
can_resend = fields.Boolean(compute='_compute_can_resend')
partner_readonly = fields.Boolean(compute='_compute_partner_readonly')
@api.depends("partner_ids")
def _compute_has_cancel(self):
self.has_cancel = self.partner_ids.filtered(lambda p: not p.resend)
def _compute_can_cancel(self):
self.can_cancel = self.partner_ids.filtered(lambda p: not p.resend)
@api.depends('partner_ids.resend')
def _compute_can_resend(self):
self.can_resend = any([partner.resend for partner in self.partner_ids])
def _compute_partner_readonly(self):
self.partner_readonly = not self.env['res.partner'].check_access_rights('write', raise_exception=False)
@@ -91,8 +96,8 @@ class PartnerResend(models.TransientModel):
_description = 'Partner with additional information for mail resend'
partner_id = fields.Many2one('res.partner', string='Partner', required=True, ondelete='cascade')
name = fields.Char(related="partner_id.name", related_sudo=False, readonly=False)
email = fields.Char(related="partner_id.email", related_sudo=False, readonly=False)
resend = fields.Boolean(string="Send Again", default=True)
name = fields.Char(related='partner_id.name', string='Recipient Name', related_sudo=False, readonly=False)
email = fields.Char(related='partner_id.email', string='Email Address', related_sudo=False, readonly=False)
resend = fields.Boolean(string='Try Again', default=True)
resend_wizard_id = fields.Many2one('mail.resend.message', string="Resend wizard")
message = fields.Char(string="Help message")
message = fields.Char(string='Error message')
@@ -9,31 +9,31 @@
<form string="Edit Partners">
<field name="mail_message_id" invisible="1"/>
<field name="notification_ids" invisible="1"/>
<field name="has_cancel" invisible="1"/>
<field name="can_resend" invisible="1"/>
<field name="partner_readonly" invisible="1"/>
<p>Select the action to do on each mail and correct the email address if needed. The modified address will be saved on the corresponding contact.</p>
<field name="partner_ids">
<tree string="Recipient" editable="top" create="0" delete="0">
<field name="name" readonly="1"/>
<field name="email" attrs="{'readonly': [('parent.partner_readonly', '=', True)]}"/>
<field name="message" readonly="1"/>
<field name="message" readonly="1" class="text-wrap"/>
<field name="partner_id" invisible="1"/>
<field name="resend" widget="boolean_toggle"/>
</tree>
</field>
<div class="alert alert-warning" role="alert" attrs="{'invisible': [('has_cancel', '=', False)]}">
<span class="fa fa-info-circle"/> Caution: It won't be possible to send this mail again to the recipients you did not select.
</div>
<footer>
<button string="Resend to selected" name="resend_mail_action" type="object" class="btn-primary o_mail_send" data-hotkey="q"/>
<button string="Ignore all failures" name="cancel_mail_action" type="object" class="btn-secondary" data-hotkey="w" />
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z" />
<button string="Send &amp; close" name="resend_mail_action" type="object" class="btn-primary o_mail_send"
attrs="{'invisible': [('can_resend', '=', False)]}" data-hotkey="q"/>
<button string="Ignore all" name="cancel_mail_action" type="object" class="btn-primary"
attrs="{'invisible': [('can_resend', '=', True)]}" data-hotkey="w"/>
<button string="Ignore all" name="cancel_mail_action" type="object" class="btn-secondary"
attrs="{'invisible': [('can_resend', '=', False)]}" data-hotkey="w"/>
<button string="Close" class="btn-secondary" special="cancel" data-hotkey="z"/>
</footer>
</form>
</field>
</record>
<record id="mail_resend_message_action" model="ir.actions.act_window">
<field name="name">Resend mail</field>
<field name="name">Sending Failures</field>
<field name="res_model">mail.resend.message</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">form</field>
@@ -9,7 +9,7 @@ class MailComposeMessage(models.TransientModel):
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing', ondelete='cascade')
campaign_id = fields.Many2one('utm.campaign', string='Mass Mailing Campaign')
mass_mailing_name = fields.Char(string='Mass Mailing Name')
mass_mailing_name = fields.Char(string='Mass Mailing Name', help='If set, a mass mailing will be created so that you can track its results in the Email Marketing app.')
mailing_list_ids = fields.Many2many('mailing.list', string='Mailing List')
def get_mail_values(self, res_ids):
@@ -13,6 +13,34 @@
<field name="mass_mailing_name"
attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}"/>
</xpath>
<xpath expr="//button[@name='action_send_mail'][not(hasclass('o_mail_send'))]" position="attributes">
<!-- 'Log' button -->
<attribute name="attrs">
{'invisible': [
'|',
('is_log', '=', False),
'&amp;',
('mass_mailing_name', '!=', ''),
('mass_mailing_name', '!=', False)
]}
</attribute>
</xpath>
<xpath expr="//button[hasclass('o_mail_send')]" position="attributes">
<!-- 'Send' button -->
<attribute name="attrs">
{'invisible': [
'|',
('is_log', '=', True),
'&amp;',
('mass_mailing_name', '!=', ''),
('mass_mailing_name', '!=', False)
]}
</attribute>
</xpath>
<xpath expr="//button[@name='action_send_mail']" position="after">
<button string="Send Mass Mailing" name="action_send_mail" type="object" class="btn-primary o_mail_send"
attrs="{'invisible': ['|', ('mass_mailing_name', '==', ''), ('mass_mailing_name', '==', False)]}" data-hotkey="q"/>
</xpath>
</field>
</record>
@@ -48,6 +48,7 @@ registerInstancePatchModel('mail.notification_group', 'sms/static/src/models/not
target: 'current',
res_model: this.res_model,
domain: [['message_has_sms_error', '=', true]],
context: { create: false },
},
});
if (this.messaging.device.isMobile) {
+12 -7
View File
@@ -11,12 +11,12 @@ class SMSRecipient(models.TransientModel):
sms_resend_id = fields.Many2one('sms.resend', required=True)
notification_id = fields.Many2one('mail.notification', required=True, ondelete='cascade')
resend = fields.Boolean(string="Resend", default=True)
resend = fields.Boolean(string='Try Again', default=True)
failure_type = fields.Selection(
related='notification_id.failure_type', related_sudo=True, readonly=True)
related='notification_id.failure_type', string='Error Message', related_sudo=True, readonly=True)
partner_id = fields.Many2one('res.partner', 'Partner', related='notification_id.res_partner_id', readonly=True)
partner_name = fields.Char('Recipient', readonly='True')
sms_number = fields.Char('Number')
partner_name = fields.Char(string='Recipient Name', readonly='True')
sms_number = fields.Char(string='Phone Number')
class SMSResend(models.TransientModel):
@@ -40,7 +40,8 @@ class SMSResend(models.TransientModel):
mail_message_id = fields.Many2one('mail.message', 'Message', readonly=True, required=True)
recipient_ids = fields.One2many('sms.resend.recipient', 'sms_resend_id', string='Recipients')
has_cancel = fields.Boolean(compute='_compute_has_cancel')
can_cancel = fields.Boolean(compute='_compute_can_cancel')
can_resend = fields.Boolean(compute='_compute_can_resend')
has_insufficient_credit = fields.Boolean(compute='_compute_has_insufficient_credit')
has_unregistered_account = fields.Boolean(compute='_compute_has_unregistered_account')
@@ -53,8 +54,12 @@ class SMSResend(models.TransientModel):
self.has_insufficient_credit = self.recipient_ids.filtered(lambda p: p.failure_type == 'sms_credit')
@api.depends("recipient_ids.resend")
def _compute_has_cancel(self):
self.has_cancel = self.recipient_ids.filtered(lambda p: not p.resend)
def _compute_can_cancel(self):
self.can_cancel = self.recipient_ids.filtered(lambda p: not p.resend)
@api.depends('recipient_ids.resend')
def _compute_can_resend(self):
self.can_resend = any([recipient.resend for recipient in self.recipient_ids])
def _check_access(self):
if not self.mail_message_id or not self.mail_message_id.model or not self.mail_message_id.res_id:
+9 -8
View File
@@ -7,29 +7,30 @@
<field name="arch" type="xml">
<form string="Edit Partners">
<field name="mail_message_id" invisible="1"/>
<field name="has_cancel" invisible="1"/>
<field name="can_resend" invisible="1"/>
<field name="has_insufficient_credit" invisible="1"/>
<field name="has_unregistered_account" invisible="1"/>
<field name="recipient_ids">
<tree string="Recipient" editable="top" create="0" delete="0">
<field name="partner_name"/>
<field name="sms_number"/>
<field name="failure_type" string="Reason"/>
<field name="failure_type" string="Reason" class="text-wrap"/>
<field name="resend" widget="boolean_toggle"/>
<field name="notification_id" invisible="1"/>
</tree>
</field>
<div class="alert alert-warning" role="alert" attrs="{'invisible': [('has_cancel', '=', False)]}">
<span class="fa fa-info-circle"/> Caution: It won't be possible to send this SMS again to the recipients you did not select.
</div>
<footer>
<button string="Buy credits" name="action_buy_credits" type="object" class="btn-primary o_mail_send"
attrs="{'invisible': [('has_insufficient_credit', '=', False)]}" data-hotkey="q"/>
<button string="Set up an account" name="action_buy_credits" type="object" class="btn-primary o_mail_send"
attrs="{'invisible': [('has_unregistered_account', '=', False)]}" data-hotkey="q"/>
<button string="Resend" name="action_resend" type="object" class="btn-primary o_mail_send" data-hotkey="w"/>
<button string="Ignore all" name="action_cancel" type="object" class="btn-secondary" data-hotkey="x"/>
<button string="Discard" class="btn-secondary" special="cancel" data-hotkey="z"/>
<button string="Send &amp; Close" name="action_resend" type="object" class="btn-primary o_mail_send"
attrs="{'invisible': ['|', ('has_unregistered_account', '=', False), ('can_resend', '=', False)]}" data-hotkey="w"/>
<button string="Ignore all" name="action_cancel" type="object" class="btn-primary"
attrs="{'invisible': ['|', '|', ('has_insufficient_credit', '=', True), ('has_unregistered_account', '=', True), '&amp;', ('has_unregistered_account', '=', True), ('can_resend', '=', True)]}" data-hotkey="x"/>
<button string="Ignore all" name="action_cancel" type="object" class="btn-secondary"
attrs="{'invisible': ['!', '|', '|', ('has_insufficient_credit', '=', True), ('has_unregistered_account', '=', True), '&amp;', ('has_unregistered_account', '=', True), ('can_resend', '=', True)]}" data-hotkey="x"/>
<button string="Close" class="btn-secondary" special="cancel" data-hotkey="z"/>
</footer>
</form>
</field>