[MERGE][IMP] snailmail: Improve integration and usability

- Improved the form and list views of snailmail.letter objects,
- Added menus to access the letters from the account app,
- Manage invalid address on the partners when trying to send a postal letter

This merge is linked to the task ID 1892473

closes odoo/odoo#28057
This commit is contained in:
Robot Odoo
2018-11-22 19:06:51 +01:00
committed by GitHub
13 changed files with 268 additions and 70 deletions
+47 -21
View File
@@ -4,10 +4,10 @@
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Project-Id-Version: Odoo Server 13.0alpha1+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-30 12:25+0000\n"
"PO-Revision-Date: 2018-10-30 12:25+0000\n"
"POT-Creation-Date: 2018-11-20 14:10+0000\n"
"PO-Revision-Date: 2018-11-20 14:10+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -16,13 +16,18 @@ msgstr ""
"Plural-Forms: \n"
#. module: snailmail
#: code:addons/snailmail/models/snailmail_letter.py:249
#, python-format
msgid "An error occured when sending the document by post.<br>Error: %s"
#: model:ir.model.fields,field_description:snailmail.field_snailmail_letter__activity_id
msgid "Activity"
msgstr ""
#. module: snailmail
#: code:addons/snailmail/models/snailmail_letter.py:215
#: code:addons/snailmail/models/snailmail_letter.py:268
#, python-format
msgid "An error occured when sending the document by post.<br>Error: %s <br>Go to 'Configuration > Management > Snailmail Letters' to see all letters awaiting dispatch to Snailmail."
msgstr ""
#. module: snailmail
#: code:addons/snailmail/models/snailmail_letter.py:234
#, python-format
msgid "An unknown error happened. Please contact the support."
msgstr ""
@@ -32,6 +37,11 @@ msgstr ""
msgid "Attachment"
msgstr ""
#. module: snailmail
#: model:ir.model.fields,field_description:snailmail.field_snailmail_letter__attachment_fname
msgid "Attachment Filename"
msgstr ""
#. module: snailmail
#: model:ir.model.fields,field_description:snailmail.field_snailmail_letter__duplex
msgid "Both side"
@@ -103,6 +113,12 @@ msgstr ""
msgid "Display Name"
msgstr ""
#. module: snailmail
#: model:ir.model.fields,field_description:snailmail.field_snailmail_letter__attachment_datas
#: model_terms:ir.ui.view,arch_db:snailmail.snailmail_letter_list
msgid "Document"
msgstr ""
#. module: snailmail
#: model:ir.model.fields,field_description:snailmail.field_snailmail_letter__res_id
msgid "Document ID"
@@ -149,8 +165,6 @@ msgid "Last Updated on"
msgstr ""
#. module: snailmail
#: model:ir.actions.act_window,name:snailmail.action_mail_letters
#: model:ir.ui.menu,name:snailmail.menu_snailmail_letters
#: model_terms:ir.ui.view,arch_db:snailmail.snailmail_letter_list
msgid "Letters"
msgstr ""
@@ -161,7 +175,7 @@ msgid "Model"
msgstr ""
#. module: snailmail
#: code:addons/snailmail/models/snailmail_letter.py:211
#: code:addons/snailmail/models/snailmail_letter.py:230
#, python-format
msgid "One or more required fields are empty."
msgstr ""
@@ -172,7 +186,7 @@ msgid "Optional report to print and attach"
msgstr ""
#. module: snailmail
#: code:addons/snailmail/models/snailmail_letter.py:264
#: code:addons/snailmail/models/snailmail_letter.py:280
#, python-format
msgid "Post letter: an error occured."
msgstr ""
@@ -202,6 +216,11 @@ msgstr ""
msgid "Recipient"
msgstr ""
#. module: snailmail
#: model:ir.model.fields,field_description:snailmail.field_snailmail_letter__reference
msgid "Related Record"
msgstr ""
#. module: snailmail
#: model:ir.model,name:snailmail.model_ir_actions_report
msgid "Report Action"
@@ -217,11 +236,22 @@ msgstr ""
msgid "Sent"
msgstr ""
#. module: snailmail
#: model:ir.model.fields,field_description:snailmail.field_snailmail_letter__user_id
msgid "Sent by"
msgstr ""
#. module: snailmail
#: model:ir.model,name:snailmail.model_snailmail_letter
msgid "Snailmail Letter"
msgstr ""
#. module: snailmail
#: model:ir.actions.act_window,name:snailmail.action_mail_letters
#: model:ir.ui.menu,name:snailmail.menu_snailmail_letters
msgid "Snailmail Letters"
msgstr ""
#. module: snailmail
#: model:ir.actions.server,name:snailmail.snailmail_print_ir_actions_server
#: model:ir.cron,cron_name:snailmail.snailmail_print
@@ -235,28 +265,23 @@ msgid "Status"
msgstr ""
#. module: snailmail
#: code:addons/snailmail/models/snailmail_letter.py:213
#: code:addons/snailmail/models/snailmail_letter.py:232
#, python-format
msgid "The attachment of the letter could not be sent. Please check its content and contact the support if the problem persists."
msgstr ""
#. module: snailmail
#: code:addons/snailmail/models/snailmail_letter.py:209
#: code:addons/snailmail/models/snailmail_letter.py:228
#, python-format
msgid "The country of the partner is not covered by Snailmail."
msgstr ""
#. module: snailmail
#: code:addons/snailmail/models/snailmail_letter.py:242
#: code:addons/snailmail/models/snailmail_letter.py:261
#, python-format
msgid "The document was correctly sent by post.<br>The tracking id is %ssend_id"
msgstr ""
#. module: snailmail
#: model:ir.model.fields,field_description:snailmail.field_snailmail_letter__user_id
msgid "User sending the letter"
msgstr ""
#. module: snailmail
#: model:ir.model.fields,help:snailmail.field_snailmail_letter__state
msgid "When a letter is created, the status is 'Draft'.\n"
@@ -265,13 +290,14 @@ msgid "When a letter is created, the status is 'Draft'.\n"
msgstr ""
#. module: snailmail
#: code:addons/snailmail/models/snailmail_letter.py:207
#: code:addons/snailmail/models/snailmail_letter.py:226
#, python-format
msgid "You don't have an IAP account registered for this service.<br>Please go to <a href=%s target=\"new\">iap.odoo.com</a> to claim your free credits."
msgstr ""
#. module: snailmail
#: code:addons/snailmail/models/snailmail_letter.py:204
#: code:addons/snailmail/models/snailmail_letter.py:223
#, python-format
msgid "You don't have enough credits to perform this operation.<br>Please go to your <a href=%s target=\"new\">iap account</a>."
msgstr ""
+1 -1
View File
@@ -6,5 +6,5 @@ from odoo import fields, models
class Company(models.Model):
_inherit = "res.company"
snailmail_color = fields.Boolean(string='Color', default=False)
snailmail_color = fields.Boolean(string='Color', default=True)
snailmail_duplex = fields.Boolean(string='Both sides', default=False)
+30 -6
View File
@@ -18,7 +18,7 @@ class SnailmailLetter(models.Model):
_name = 'snailmail.letter'
_description = 'Snailmail Letter'
user_id = fields.Many2one('res.users', 'User sending the letter')
user_id = fields.Many2one('res.users', 'Sent by')
model = fields.Char('Model', required=True)
res_id = fields.Integer('Document ID', required=True)
partner_id = fields.Many2one('res.partner', string='Recipient', required=True)
@@ -27,6 +27,9 @@ class SnailmailLetter(models.Model):
report_template = fields.Many2one('ir.actions.report', 'Optional report to print and attach')
attachment_id = fields.Many2one('ir.attachment', string='Attachment', ondelete='cascade')
attachment_datas = fields.Binary('Document', related='attachment_id.datas')
attachment_fname = fields.Char('Attachment Filename', related='attachment_id.datas_fname')
activity_id = fields.Many2one('mail.activity', string='Activity')
color = fields.Boolean(string='Color', default=lambda self: self.env.user.company_id.snailmail_color)
duplex = fields.Boolean(string='Both side', default=lambda self: self.env.user.company_id.snailmail_duplex)
state = fields.Selection([
@@ -40,7 +43,23 @@ class SnailmailLetter(models.Model):
"If the letter is correctly sent, the status goes in 'Sent',\n"
"If not, it will got in state 'Error' and the error message will be displayed in the field 'Error Message'.")
info_msg = fields.Char('Information')
display_name = fields.Char('Display Name', compute="_compute_display_name")
reference = fields.Char(string='Related Record', compute='_compute_reference', readonly=True, store=False)
@api.depends('reference', 'partner_id')
def _compute_display_name(self):
for letter in self:
if letter.attachment_id:
letter.display_name = "%s - %s" % (letter.attachment_id.name, letter.partner_id.name)
else:
letter.display_name = letter.partner_id.name
@api.depends('model', 'res_id')
def _compute_reference(self):
for res in self:
res.reference = "%s,%s" % (res.model, res.res_id)
@api.multi
def _fetch_attachment(self):
"""
@@ -192,7 +211,7 @@ class SnailmailLetter(models.Model):
'options': {
'color': self and self[0].color,
'duplex': self and self[0].duplex,
'currency_name': self and self[0].company_id.currency_id.name,
'currency_name': 'EUR',
},
# this will not raise the InsufficientCreditError which is the behaviour we want for now
'batch': True,
@@ -245,7 +264,7 @@ class SnailmailLetter(models.Model):
else:
# look for existing activities related to snailmail to update or create a new one.
# TODO: in following versions, Add a link to a specifc activity on the letter
note = _('An error occured when sending the document by post.<br>Error: %s' % \
note = _('An error occured when sending the document by post.<br>Error: %s <br>Go to \'Configuration > Management > Snailmail Letters\' to see all letters awaiting dispatch to Snailmail.' % \
self._get_error_message(doc['error'] if response['request_code'] == 200 else response['reason']))
domain = [
@@ -254,8 +273,7 @@ class SnailmailLetter(models.Model):
('res_model_id', '=', self.env['ir.model']._get(letter.model).id),
('activity_type_id', '=', self.env.ref('mail.mail_activity_data_warning').id),
]
MailActivity = self.env['mail.activity']
activity = MailActivity.search(domain, limit=1)
activity = letter.activity_id
activity_data = {
'activity_type_id': self.env.ref('mail.mail_activity_data_warning').id,
@@ -271,7 +289,8 @@ class SnailmailLetter(models.Model):
'res_id': letter.res_id,
'res_model_id': self.env['ir.model']._get(letter.model).id,
})
MailActivity.create(activity_data)
activity = self.env['mail.activity'].create(activity_data)
letter.write({'activity_id': activity.id})
letter.write({'info_msg': note, 'state': 'error'})
@@ -285,6 +304,11 @@ class SnailmailLetter(models.Model):
def cancel(self):
self.write({'state': 'canceled'})
@api.multi
def unlink(self):
self.mapped('activity_id').unlink()
return super(SnailmailLetter, self).unlink()
@api.multi
def _snailmail_estimate(self):
"""
+19 -14
View File
@@ -5,11 +5,10 @@
<field name="model">snailmail.letter</field>
<field name="arch" type="xml">
<tree decoration-danger="state=='error'" decoration-muted="state=='sent'" string="Letters">
<field name="attachment_id"/>
<field name="model"/>
<field name="res_id"/>
<field name="company_id"/>
<field name="state"/>
<field name="attachment_id" string="Document"/>
<field name="partner_id"/>
<field name="user_id"/>
<field name="state" invisible="1"/>
<field name="info_msg" widget="html"/>
</tree>
</field>
@@ -26,16 +25,22 @@
<field name="state" widget="statusbar" statusbar_visible="pending,sent,cancel"/>
</header>
<sheet>
<div class="oe_title">
<h1><field name="display_name" readonly="1"/></h1>
</div>
<group>
<field name="attachment_id"/>
<field name="partner_id"/>
<field name="user_id"/>
<field name="model"/>
<field name="res_id"/>
<field name="color"/>
<field name="duplex"/>
<field name="reference" widget="reference" readonly="1"/>
<field name="attachment_datas" filename="attachment_fname" readonly="1" />
<field name="attachment_fname" invisible="1"/>
<field name="partner_id" readonly="1"/>
<field name="user_id" readonly="1"/>
<field name="info_msg" widget="html" readonly="1"/>
<field name="company_id"/>
</group>
<group groups="base.group_no_one">
<field name="model" readonly="1"/>
<field name="res_id" readonly="1"/>
<field name="color" readonly="1"/>
<field name="duplex" readonly="1"/>
</group>
</sheet>
</form>
@@ -43,7 +48,7 @@
</record>
<record model="ir.actions.act_window" id="action_mail_letters">
<field name="name">Letters</field>
<field name="name">Snailmail Letters</field>
<field name="res_model">snailmail.letter</field>
<field name="view_type">form</field>
<field name="view_mode">form,tree</field>
+2
View File
@@ -10,6 +10,8 @@ Allows users to send invoices by post
'depends': ['account', 'snailmail'],
'data': [
'views/res_config_settings_views.xml',
'views/snailmail_views.xml',
'views/assets.xml',
'wizard/account_invoice_send_views.xml',
],
'auto_install': True,
@@ -4,10 +4,10 @@
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Project-Id-Version: Odoo Server 13.0alpha1+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-11-05 09:29+0000\n"
"PO-Revision-Date: 2018-11-05 09:29+0000\n"
"POT-Creation-Date: 2018-11-20 14:11+0000\n"
"PO-Revision-Date: 2018-11-20 14:11+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -15,6 +15,19 @@ msgstr ""
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: snailmail_account
#: code:addons/snailmail_account/wizard/account_invoice_send.py:86
#, python-format
msgid "%s of the selected invoice(s) had an invalid address and were not sent"
msgstr ""
#. module: snailmail_account
#: model_terms:ir.ui.view,arch_db:snailmail_account.account_invoice_send_inherit_account_wizard_form
msgid "<span class=\"text-danger\">\n"
" Incomplete customer address\n"
" </span>"
msgstr ""
#. module: snailmail_account
#: model_terms:ir.ui.view,arch_db:snailmail_account.account_invoice_send_inherit_account_wizard_form
msgid "<span> to: </span>"
@@ -30,6 +43,13 @@ msgstr ""
msgid "Allows to send the document by snail mail (coventional posting delivery service)"
msgstr ""
#. module: snailmail_account
#: code:addons/snailmail_account/wizard/account_invoice_send.py:89
#, python-format
msgid "Cannot send by post to an incomplete address.\n"
" Please update the customer's address or uncheck 'Send by Post'."
msgstr ""
#. module: snailmail_account
#: model:ir.model,name:snailmail_account.model_res_company
msgid "Companies"
@@ -41,8 +61,10 @@ msgid "Config Settings"
msgstr ""
#. module: snailmail_account
#: model:ir.model.fields,field_description:snailmail_account.field_account_invoice_send__currency_id
msgid "Currency"
#: code:addons/snailmail_account/wizard/account_invoice_send.py:85
#: model:ir.model.fields,field_description:snailmail_account.field_account_invoice_send__invalid_addresses
#, python-format
msgid "Invalid Addresses"
msgstr ""
#. module: snailmail_account
@@ -57,7 +79,7 @@ msgstr ""
#. module: snailmail_account
#: model_terms:ir.ui.view,arch_db:snailmail_account.res_config_settings_view_form
msgid "Print In Color"
msgid "Print in Color"
msgstr ""
#. module: snailmail_account
@@ -72,11 +94,27 @@ msgstr ""
#. module: snailmail_account
#: model:ir.model.fields,field_description:snailmail_account.field_account_invoice_send__snailmail_is_letter
#: model:ir.model.fields,field_description:snailmail_account.field_res_config_settings__invoice_is_snailmail
msgid "Send by Post"
msgstr ""
#. module: snailmail_account
#: model:ir.model.fields,field_description:snailmail_account.field_res_config_settings__invoice_is_snailmail
msgid "Send by Post with Snailmail"
msgstr ""
#. module: snailmail_account
#: model:ir.actions.act_window,name:snailmail_account.action_mail_invoice_letters
#: model:ir.ui.menu,name:snailmail_account.menu_snailmail_invoice_letters
msgid "Snailmail Letters"
msgstr ""
#. module: snailmail_account
#: model:ir.model.fields,field_description:snailmail_account.field_account_invoice_send__snailmail_cost
msgid "Stamp(s)"
msgstr ""
#. module: snailmail_account
#: model_terms:ir.ui.view,arch_db:snailmail_account.account_invoice_send_inherit_account_wizard_form
msgid "invoice(s) do not have a complete customer address"
msgstr ""
@@ -7,4 +7,4 @@ from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
invoice_is_snailmail = fields.Boolean(string='Send by Post', related='company_id.invoice_is_snailmail', readonly=False)
invoice_is_snailmail = fields.Boolean(string='Send by Post with Snailmail', related='company_id.invoice_is_snailmail', readonly=False)
@@ -0,0 +1,35 @@
odoo.define('snailmail_account.NotificationManager', function (require) {
"use strict";
var AbstractService = require('web.AbstractService');
var core = require("web.core");
var SnailmailNotificationManager = AbstractService.extend({
dependencies: ['bus_service'],
/**
* @override
*/
start: function () {
this._super.apply(this, arguments);
this.call('bus_service', 'onNotification', this, this._onNotification);
},
_onNotification: function (notifs) {
var self = this;
_.each(notifs, function (notif) {
var model = notif[0][1];
var type = notif[1].type;
if (model === 'res.partner' && type === 'snailmail_invalid_address') {
self.do_warn(notif[1].title, notif[1].message);
}
});
}
});
core.serviceRegistry.add('snailmail_notification_service', SnailmailNotificationManager);
return SnailmailNotificationManager;
});
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_backend" name="iap assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/snailmail_account/static/src/js/snailmail_account_notification_manager.js"></script>
</xpath>
</template>
</odoo>
@@ -16,12 +16,12 @@
<span class="fa fa-lg fa-building-o" title="Values set here are company-specific."/>
<div class="content-group" attrs="{'invisible': [('invoice_is_snailmail' ,'=', False)]}">
<div class="mt16">
<field name="snailmail_duplex" class="oe_inline"/>
<label string="Print on Both Sides" class="o_light_label" for="snailmail_duplex"/>
<field name="snailmail_color" class="oe_inline"/>
<label string="Print in Color" class="o_light_label" for="snailmail_color"/>
</div>
<div>
<field name="snailmail_color" class="oe_inline"/>
<label string="Print In Color" class="o_light_label" for="snailmail_color"/>
<field name="snailmail_duplex" class="oe_inline"/>
<label string="Print on Both Sides" class="o_light_label" for="snailmail_duplex"/>
</div>
</div>
</div>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record model="ir.actions.act_window" id="action_mail_invoice_letters">
<field name="name">Snailmail Letters</field>
<field name="res_model">snailmail.letter</field>
<field name="view_type">form</field>
<field name="view_mode">form,tree</field>
<field name="domain">[('state', '!=', 'draft'), ('model','=','account.invoice')]</field>
<field name="view_id" ref="snailmail.snailmail_letter_list" />
</record>
<menuitem id="menu_snailmail_invoice_letters" parent="account.account_management_menu" action="action_mail_invoice_letters"
sequence="10"/>
</odoo>
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class AccountInvoiceSend(models.TransientModel):
@@ -12,8 +13,20 @@ class AccountInvoiceSend(models.TransientModel):
partner_id = fields.Many2one('res.partner', compute='_get_partner', string='Partner')
snailmail_is_letter = fields.Boolean('Send by Post', help='Allows to send the document by snail mail (coventional posting delivery service)', default=lambda self: self.env.user.company_id.invoice_is_snailmail)
snailmail_cost = fields.Float(string='Stamp(s)', compute='_snailmail_estimate', store=True, readonly=True)
currency_id = fields.Many2one('res.currency', compute='_get_invoice_currency', string="Currency")
letter_ids = fields.Many2many('snailmail.letter', 'snailmail_letter_account_invoice_send_rel', ondelete='cascade')
invalid_addresses = fields.Integer('Invalid Addresses', compute='_compute_invalid_addresses')
@api.depends('snailmail_cost', 'letter_ids')
def _compute_invalid_addresses(self):
for wizard in self:
count_invalid_addresses = 0
required_fields = ['street', 'city', 'zip', 'country_id']
for letter in wizard.letter_ids:
for field in required_fields:
if not letter.partner_id[field]:
count_invalid_addresses += 1
break
wizard.invalid_addresses = count_invalid_addresses
@api.multi
@api.onchange('invoice_ids')
@@ -72,6 +85,18 @@ class AccountInvoiceSend(models.TransientModel):
@api.multi
def send_and_print_action(self):
if self.snailmail_is_letter and self.invalid_addresses:
if self.composition_mode == "mass_mail":
self.env['bus.bus'].sendone(
(self._cr.dbname, 'res.partner', self.env.user.partner_id.id),
{'type': 'snailmail_invalid_address', 'title': _("Invalid Addresses"),
'message': _("%s of the selected invoice(s) had an invalid address and were not sent") % self.invalid_addresses}
)
else:
raise UserError(_(
'''Cannot send by post to an incomplete address.
Please update the customer's address or uncheck \'Send by Post\'.'''
))
res = super(AccountInvoiceSend, self).send_and_print_action()
if self.snailmail_is_letter:
self.snailmail_print_action()
@@ -9,25 +9,44 @@
<xpath expr="//div[@name='option_email']" position='before'>
<div name="option_letter">
<field name="letter_ids" invisible="1"/>
<field name="invalid_addresses" invisible="1"/>
<div name="option" class="text-left d-inline-block">
<field name="snailmail_is_letter" />
<b><label for="snailmail_is_letter"/></b>
</div>
<div class="text-left d-inline-block mr8" name='cost' attrs="{'invisible': [('snailmail_is_letter','=', False)]}">
<b>(
<span>
<field name="snailmail_cost" options="{'digits':[0,0]}" class="mr4"/>
<label for="snailmail_cost" class="mr4"/>
<span attrs="{'invisible': [('snailmail_is_letter','=', False)]}">
<span class="mr4" attrs="{'invisible': [('snailmail_cost', '=', 0)]}">
<b>(
<span>
<field name="snailmail_cost" options="{'digits':[0,0]}" class="mr4"/>
<label for="snailmail_cost" class="mr4"/>
</span>
<i class="fa fa-info-circle" role="img" aria-label="Warning" title="Make sure you have enough Stamps on your account."/>
)</b>
</span>
<span attrs="{'invisible': [('composition_mode', '=', 'mass_mail')]}">
<span attrs="{'invisible': [('invalid_addresses', '!=', 0)]}">
<div class="text-right text-muted d-inline-block" name="address">
<span> to: </span>
<field name="partner_id" readonly="1" force_save="1" context="{'show_address': 1, 'address_inline': 1}" options="{'always_reload': True, 'no_quick_create': True}"/>
</div>
</span>
<i class="fa fa-info-circle" role="img" aria-label="Warning" title="
The letter will be sent using the IAP service from Odoo.&#10;Make sure you have enough credits on your account or proceed to a recharge.&#10;The price is estimated based on 1 page per document.
"/>
)</b>
</div>
<div class="text-right text-muted d-inline-block" name="address" attrs="{'invisible': ['|', ('composition_mode', '=', 'mass_mail'), ('snailmail_is_letter','=', False)]}">
<span> to: </span>
<field name="partner_id" readonly="1" force_save="1" context="{'show_address': 1, 'address_inline': 1}" options="{'always_reload': True, 'no_quick_create': True}"/>
</div>
<span attrs="{'invisible': [('invalid_addresses', '=', 0)]}">
<div class="text-right d-inline-block" attrs="{'invisible': [('composition_mode', '=', 'mass_mail')]}">
<span class="text-danger">
Incomplete customer address
</span>
</div>
</span>
</span>
<span attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}">
<span attrs="{'invisible': [('snailmail_cost', '=', 0)]}">
<span class="text-danger" attrs="{'invisible': [('invalid_addresses', '=', 0)]}">
<field name="invalid_addresses" readonly="1" options="{'digits':[0,0]}"/> invoice(s) do not have a complete customer address
</span>
</span>
</span>
</span>
</div>
</xpath>
<xpath expr="//footer/button[hasclass('send_and_print')]" position='attributes'>