[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:
@@ -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) {
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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,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 & 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),
|
||||
'&',
|
||||
('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),
|
||||
'&',
|
||||
('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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 & 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), '&', ('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), '&', ('has_unregistered_account', '=', True), ('can_resend', '=', True)]}" data-hotkey="x"/>
|
||||
<button string="Close" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user