diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 77469f2fe94..076dffd61b7 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -487,14 +487,7 @@ class Message(models.Model):
'moderation_status': 'pending_moderation'
}
"""
- message_values = self.read([
- 'id', 'body', 'date', 'author_id', 'email_from', # base message fields
- 'message_type', 'subtype_id', 'subject', # message specific
- 'model', 'res_id', 'record_name', # document related
- 'channel_ids', 'partner_ids', # recipients
- 'starred_partner_ids', # list of partner ids for whom the message is starred
- 'moderation_status',
- ])
+ message_values = self.read(self._get_message_format_fields())
message_tree = dict((m.id, m) for m in self.sudo())
self._message_read_dict_postprocess(message_values, message_tree)
@@ -526,6 +519,17 @@ class Message(models.Model):
message['module_icon'] = modules.module.get_module_icon(self.env[message['model']]._original_module)
return message_values
+ @api.multi
+ def _get_message_format_fields(self):
+ return [
+ 'id', 'body', 'date', 'author_id', 'email_from', # base message fields
+ 'message_type', 'subtype_id', 'subject', # message specific
+ 'model', 'res_id', 'record_name', # document related
+ 'channel_ids', 'partner_ids', # recipients
+ 'starred_partner_ids', # list of partner ids for whom the message is starred
+ 'moderation_status',
+ ]
+
@api.multi
def _format_mail_failures(self):
"""
diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js
index 4342b1e244a..dc98bb7195b 100644
--- a/addons/mail/static/src/js/models/messages/message.js
+++ b/addons/mail/static/src/js/models/messages/message.js
@@ -46,22 +46,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
Mixins.EventDispatcherMixin.init.call(this, arguments);
this.setParent(parent);
- this._customerEmailData = data.customer_email_data || [];
- this._customerEmailStatus = data.customer_email_status;
- this._documentModel = data.model;
- this._documentName = data.record_name;
- this._documentID = data.res_id;
- this._emailFrom = data.email_from;
- this._info = data.info;
- this._moduleIcon = data.module_icon;
- this._needactionPartnerIDs = data.needaction_partner_ids || [];
- this._starredPartnerIDs = data.starred_partner_ids || [];
- this._subject = data.subject;
- this._subtypeDescription = data.subtype_description;
- this._threadIDs = data.channel_ids || [];
- this._trackingValueIDs = data.tracking_value_ids;
-
- this._moderationStatus = data.moderation_status || 'accepted';
+ this._setInitialData(data);
this._processBody(emojis);
this._processMailboxes();
@@ -729,6 +714,44 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
});
}
},
+ /**
+ * @private
+ * @param {Object} data
+ * @param {string} [data.body = ""]
+ * @param {(string|integer)[]} [data.channel_ids]
+ * @param {Object[]} [data.customer_email_data]
+ * @param {string} [data.customer_email_status]
+ * @param {string} [data.email_from]
+ * @param {string} [data.info]
+ * @param {string} [data.model]
+ * @param {string} [data.moderation_status='accepted']
+ * @param {string} [data.module_icon]
+ * @param {Array} [data.needaction_partner_ids = []]
+ * @param {string} [data.record_name]
+ * @param {integer} [data.res_id]
+ * @param {Array} [data.starred_partner_ids = []]
+ * @param {string} [data.subject]
+ * @param {string} [data.subtype_description]
+ * @param {Object[]} [data.tracking_value_ids]
+ */
+ _setInitialData: function (data){
+ this._customerEmailData = data.customer_email_data || [];
+ this._customerEmailStatus = data.customer_email_status;
+ this._documentModel = data.model;
+ this._documentName = data.record_name;
+ this._documentID = data.res_id;
+ this._emailFrom = data.email_from;
+ this._info = data.info;
+ this._moduleIcon = data.module_icon;
+ this._needactionPartnerIDs = data.needaction_partner_ids || [];
+ this._starredPartnerIDs = data.starred_partner_ids || [];
+ this._subject = data.subject;
+ this._subtypeDescription = data.subtype_description;
+ this._threadIDs = data.channel_ids || [];
+ this._trackingValueIDs = data.tracking_value_ids;
+
+ this._moderationStatus = data.moderation_status || 'accepted';
+ },
/**
* Set whether the message is moderated by current user or not.
* If it is moderated by the current user, the message is moved to the
diff --git a/addons/snailmail/__init__.py b/addons/snailmail/__init__.py
index cfa5456e4ac..362dae520e1 100644
--- a/addons/snailmail/__init__.py
+++ b/addons/snailmail/__init__.py
@@ -2,3 +2,4 @@
from . import models
from . import country_utils
+from . import wizard
diff --git a/addons/snailmail/__manifest__.py b/addons/snailmail/__manifest__.py
index 4555917d3ee..4043d079cba 100644
--- a/addons/snailmail/__manifest__.py
+++ b/addons/snailmail/__manifest__.py
@@ -12,7 +12,14 @@ Allows users to send documents by post
'data/snailmail_data.xml',
'views/report_assets.xml',
'views/snailmail_views.xml',
+ 'views/assets.xml',
+ 'wizard/snailmail_letter_cancel_views.xml',
+ 'wizard/snailmail_letter_format_error_views.xml',
+ 'wizard/snailmail_letter_missing_required_fields_views.xml',
'security/ir.model.access.csv',
],
+ 'qweb': [
+ 'static/src/xml/thread.xml',
+ ],
'auto_install': True,
}
diff --git a/addons/snailmail/models/__init__.py b/addons/snailmail/models/__init__.py
index 3ef34136bd1..a05bee36be9 100644
--- a/addons/snailmail/models/__init__.py
+++ b/addons/snailmail/models/__init__.py
@@ -6,3 +6,4 @@ from . import res_config_settings
from . import snailmail_letter
from . import ir_actions_report
from . import ir_qweb_fields
+from . import mail_message
diff --git a/addons/snailmail/models/mail_message.py b/addons/snailmail/models/mail_message.py
new file mode 100644
index 00000000000..f649455ca70
--- /dev/null
+++ b/addons/snailmail/models/mail_message.py
@@ -0,0 +1,48 @@
+
+from odoo import api, fields, models
+
+class Message(models.Model):
+ _inherit = 'mail.message'
+
+ snailmail_error = fields.Boolean("Snailmail message in error", compute="_compute_snailmail_error", search="_search_snailmail_error")
+ snailmail_status = fields.Char("Snailmail Status", compute="_compute_snailmail_error")
+ letter_ids = fields.One2many(comodel_name='snailmail.letter', inverse_name='message_id')
+ message_type = fields.Selection(selection_add=[('snailmail', 'Snailmail')])
+
+ @api.multi
+ def _get_message_format_fields(self):
+ res = super(Message, self)._get_message_format_fields()
+ res.append('snailmail_error')
+ res.append('snailmail_status')
+ return res
+
+ @api.multi
+ @api.depends('letter_ids', 'letter_ids.state')
+ def _compute_snailmail_error(self):
+ for message in self:
+ if message.message_type == 'snailmail' and message.letter_ids:
+ message.snailmail_error = message.letter_ids[0].state == 'error'
+ message.snailmail_status = message.letter_ids[0].error_code if message.letter_ids[0].state == 'error' else message.letter_ids[0].state
+ else:
+ message.snailmail_error = False
+ message.snailmail_status = ''
+
+ @api.multi
+ def _search_snailmail_error(self, operator, operand):
+ if operator == '=' and operand:
+ return ['&', ('letter_ids.state', '=', 'error'), ('letter_ids.user_id', '=', self.env.user.id)]
+ return ['!', '&', ('letter_ids.state', '=', 'error'), ('letter_ids.user_id', '=', self.env.user.id)]
+
+ @api.multi
+ def cancel_letter(self):
+ self.mapped('letter_ids').cancel()
+
+ @api.multi
+ def send_letter(self):
+ self.mapped('letter_ids')._snailmail_print()
+
+ @api.multi
+ def message_fetch_failed(self):
+ res = super(Message, self).message_fetch_failed()
+ failed_letters = self.letter_ids.fetch_failed_letters()
+ return res + failed_letters
diff --git a/addons/snailmail/models/res_company.py b/addons/snailmail/models/res_company.py
index 25e694968da..2aa46f3ca43 100644
--- a/addons/snailmail/models/res_company.py
+++ b/addons/snailmail/models/res_company.py
@@ -7,4 +7,5 @@ class Company(models.Model):
_inherit = "res.company"
snailmail_color = fields.Boolean(string='Color', default=True)
+ snailmail_cover = fields.Boolean(string='Add a Cover Page', default=False)
snailmail_duplex = fields.Boolean(string='Both sides', default=False)
diff --git a/addons/snailmail/models/res_config_settings.py b/addons/snailmail/models/res_config_settings.py
index bf79ec83c3d..84365f21dc8 100644
--- a/addons/snailmail/models/res_config_settings.py
+++ b/addons/snailmail/models/res_config_settings.py
@@ -8,4 +8,5 @@ class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
snailmail_color = fields.Boolean(string='Print In Color', related='company_id.snailmail_color', readonly=False)
+ snailmail_cover = fields.Boolean(string='Add a Cover Page', related='company_id.snailmail_cover', readonly=False)
snailmail_duplex = fields.Boolean(string='Print Both sides', related='company_id.snailmail_duplex', readonly=False)
diff --git a/addons/snailmail/models/res_partner.py b/addons/snailmail/models/res_partner.py
index 449559d2e9d..c4b3b576bf0 100644
--- a/addons/snailmail/models/res_partner.py
+++ b/addons/snailmail/models/res_partner.py
@@ -9,6 +9,20 @@ from odoo.addons.snailmail.country_utils import SNAILMAIL_COUNTRIES
class ResPartner(models.Model):
_inherit = "res.partner"
+ @api.multi
+ def write(self, vals):
+ letter_address_vals = {}
+ address_fields = ['street', 'street2', 'city', 'zip', 'state_id', 'country_id']
+ for field in address_fields:
+ if field in vals:
+ letter_address_vals[field] = vals[field]
+
+ if len(letter_address_vals):
+ letter_ids = self.env['snailmail.letter'].search([('state', 'not in', ['sent', 'canceled']), ('partner_id', '=', self.id)])
+ letter_ids.write(letter_address_vals)
+
+ return super(ResPartner, self).write(vals)
+
@api.multi
def _get_country_name(self):
# when sending a letter, thus rendering the report with the snailmail_layout,
@@ -23,7 +37,7 @@ class ResPartner(models.Model):
@api.model
def _get_address_format(self):
# When sending a letter, the fields 'street' and 'street2' should be on a single line to fit in the address area
- if self.env.context.get('snailmail_layout'):
+ if self.env.context.get('snailmail_layout') and self.street2:
return "%(street)s, %(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s"
return super(ResPartner, self)._get_address_format()
\ No newline at end of file
diff --git a/addons/snailmail/models/snailmail_letter.py b/addons/snailmail/models/snailmail_letter.py
index c2e3a241b1f..bddadf07290 100644
--- a/addons/snailmail/models/snailmail_letter.py
+++ b/addons/snailmail/models/snailmail_letter.py
@@ -13,6 +13,15 @@ DEFAULT_ENDPOINT = 'https://iap-snailmail.odoo.com'
ESTIMATE_ENDPOINT = '/iap/snailmail/1/estimate'
PRINT_ENDPOINT = '/iap/snailmail/1/print'
+ERROR_CODES = [
+ 'MISSING_REQUIRED_FIELDS',
+ 'CREDIT_ERROR',
+ 'TRIAL_ERROR',
+ 'NO_PRICE_AVAILABLE',
+ 'FORMAT_ERROR',
+ 'UNKNOWN_ERROR',
+]
+
class SnailmailLetter(models.Model):
_name = 'snailmail.letter'
@@ -29,24 +38,33 @@ class SnailmailLetter(models.Model):
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)
+ cover = fields.Boolean(string='Cover Page', default=lambda self: self.env.user.company_id.snailmail_cover)
duplex = fields.Boolean(string='Both side', default=lambda self: self.env.user.company_id.snailmail_duplex)
state = fields.Selection([
- ('draft', 'Draft'),
('pending', 'In Queue'),
('sent', 'Sent'),
('error', 'Error'),
('canceled', 'Canceled')
- ], 'Status', readonly=True, copy=False, default='draft',
- help="When a letter is created, the status is 'Draft'.\n"
+ ], 'Status', readonly=True, copy=False, default='pending', required=True,
+ help="When a letter is created, the status is 'Pending'.\n"
"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'.")
+ error_code = fields.Selection([(err_code, err_code) for err_code in ERROR_CODES], string="Error")
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)
+ message_id = fields.Many2one('mail.message', string="Snailmail Status Message")
+
+ street = fields.Char('Street')
+ street2 = fields.Char('Street2')
+ zip = fields.Char('Zip')
+ city = fields.Char('City')
+ state_id = fields.Many2one("res.country.state", string='State')
+ country_id = fields.Many2one('res.country', string='Country')
+
@api.depends('reference', 'partner_id')
def _compute_display_name(self):
for letter in self:
@@ -59,7 +77,25 @@ class SnailmailLetter(models.Model):
def _compute_reference(self):
for res in self:
res.reference = "%s,%s" % (res.model, res.res_id)
-
+
+ @api.model
+ def create(self, vals):
+ msg_id = self.env[vals['model']].browse(vals['res_id']).message_post(
+ body=_("Letter sent by post with Snailmail"),
+ message_type='snailmail'
+ )
+ partner_id = self.env['res.partner'].browse(vals['partner_id'])
+ vals.update({
+ 'message_id': msg_id.id,
+ 'street': partner_id.street,
+ 'street2': partner_id.street2,
+ 'zip': partner_id.zip,
+ 'city': partner_id.city,
+ 'state_id': partner_id.state_id.id,
+ 'country_id': partner_id.country_id.id,
+ })
+ return super(SnailmailLetter, self).create(vals)
+
@api.multi
def _fetch_attachment(self):
"""
@@ -85,7 +121,7 @@ class SnailmailLetter(models.Model):
else:
report_name = 'Document'
filename = "%s.%s" % (report_name, "pdf")
- pdf_bin, _ = report.with_context(snailmail_layout=True).render_qweb_pdf(self.res_id)
+ pdf_bin, _ = report.with_context(snailmail_layout=not self.cover).render_qweb_pdf(self.res_id)
attachment = self.env['ir.attachment'].create({
'name': filename,
'datas': base64.b64encode(pdf_bin),
@@ -157,6 +193,7 @@ class SnailmailLetter(models.Model):
'letter_id': letter.id,
'res_model': letter.model,
'res_id': letter.res_id,
+ 'contact_address': letter.partner_id.with_context(snailmail_layout=True, show_address=True).name_get()[0][1],
'address': {
'name': letter.partner_id.name,
'street': letter.partner_id.street,
@@ -196,6 +233,7 @@ class SnailmailLetter(models.Model):
letter.write({
'info_msg': 'The attachment could not be generated.',
'state': 'error',
+ 'error_code': 'ATTACHMENT_ERROR'
})
continue
if letter.company_id.external_report_layout_id == self.env.ref('l10n_de.external_layout_din5008', False):
@@ -210,6 +248,7 @@ class SnailmailLetter(models.Model):
'documents': documents,
'options': {
'color': self and self[0].color,
+ 'cover': self and self[0].cover,
'duplex': self and self[0].duplex,
'currency_name': 'EUR',
},
@@ -235,7 +274,26 @@ class SnailmailLetter(models.Model):
return error
@api.multi
- def _snailmail_print(self):
+ def _snailmail_print(self, immediate=True):
+ valid_address_letters = self.filtered(lambda l: l._is_valid_address(l))
+ invalid_address_letters = self - valid_address_letters
+ invalid_address_letters._snailmail_print_invalid_address()
+ if valid_address_letters and immediate:
+ valid_address_letters._snailmail_print_valid_address()
+ self.env.cr.commit()
+
+ @api.multi
+ def _snailmail_print_invalid_address(self):
+ for letter in self:
+ letter.write({
+ 'state': 'error',
+ 'error_code': 'MISSING_REQUIRED_FIELDS',
+ 'info_msg': _('The address of the recipient is not complete')
+ })
+ self.send_snailmail_update()
+
+ @api.multi
+ def _snailmail_print_valid_address(self):
"""
get response
{
@@ -249,52 +307,36 @@ class SnailmailLetter(models.Model):
}
}
"""
- self.write({'state': 'pending'})
endpoint = self.env['ir.config_parameter'].sudo().get_param('snailmail.endpoint', DEFAULT_ENDPOINT)
params = self._snailmail_create('print')
response = jsonrpc(endpoint + PRINT_ENDPOINT, params=params)
for doc in response['request']['documents']:
- letter = self.browse(doc['letter_id'])
- record = self.env[doc['res_model']].browse(doc['res_id'])
if doc.get('sent') and response['request_code'] == 200:
- if hasattr(record, '_message_log'):
- message = _('The document was correctly sent by post.
The tracking id is %s' % doc['send_id'])
- record._message_log(body=message)
- letter.write({'info_msg': message, 'state': 'sent'})
+ note = _('The document was correctly sent by post.
The tracking id is %s' % doc['send_id'])
+ letter_data = {'info_msg': note, 'state': 'sent', 'error_code': False}
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.
Error: %s
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']))
+ error = doc['error'] if response['request_code'] == 200 else response['reason']
- domain = [
- ('summary', 'ilike', '[SNAILMAIL]'),
- ('res_id', '=', letter.res_id),
- ('res_model_id', '=', self.env['ir.model']._get(letter.model).id),
- ('activity_type_id', '=', self.env.ref('mail.mail_activity_data_warning').id),
- ]
- activity = letter.activity_id
-
- activity_data = {
- 'activity_type_id': self.env.ref('mail.mail_activity_data_warning').id,
- 'summary': '[SNAILMAIL] ' + _('Post letter: an error occured.'),
- 'note': note,
- 'date_deadline': fields.Date.today()
+ note = _('An error occured when sending the document by post.
Error: %s' % self._get_error_message(error))
+ letter_data = {
+ 'info_msg': note,
+ 'state': 'error',
+ 'error_code': error if error in ERROR_CODES else 'UNKNOWN_ERROR'
}
- if activity:
- activity.update(activity_data)
- else:
- activity_data.update({
- 'user_id': letter.user_id.id,
- 'res_id': letter.res_id,
- 'res_model_id': self.env['ir.model']._get(letter.model).id,
- })
- activity = self.env['mail.activity'].create(activity_data)
- letter.write({'activity_id': activity.id})
- letter.write({'info_msg': note, 'state': 'error'})
+ letter = self.browse(doc['letter_id'])
+ letter.write(letter_data)
+ self.send_snailmail_update()
- self.env.cr.commit()
+ @api.multi
+ def send_snailmail_update(self):
+ notifications = []
+ for letter in self:
+ notifications.append([
+ (self._cr.dbname, 'res.partner', letter.user_id.partner_id.id),
+ {'type': 'snailmail_update', 'elements': letter._format_snailmail_failures()}
+ ])
+ self.env['bus.bus'].sendmany(notifications)
@api.multi
def snailmail_print(self):
@@ -302,12 +344,8 @@ class SnailmailLetter(models.Model):
@api.multi
def cancel(self):
- self.write({'state': 'canceled'})
-
- @api.multi
- def unlink(self):
- self.mapped('activity_id').unlink()
- return super(SnailmailLetter, self).unlink()
+ self.write({'state': 'canceled', 'error_code': False})
+ self.send_snailmail_update()
@api.multi
def _snailmail_estimate(self):
@@ -343,18 +381,76 @@ class SnailmailLetter(models.Model):
return req['total_cost']
+ @api.model
+ def _snailmail_estimate_from_documents(self, res_model, res_ids, partner_field=None):
+ endpoint = self.env['ir.config_parameter'].sudo().get_param('snailmail.endpoint', DEFAULT_ENDPOINT)
+ docs = self.env[res_model].browse(res_ids)
+ doc_list = []
+ for doc in docs:
+ if partner_field:
+ country_code = doc[partner_field].country_id.code
+ else:
+ country_code = doc.country_id.code
+ val = {
+ 'pages': 1,
+ 'address': {
+ 'country_code': country_code,
+ },
+ }
+ doc_list.append(val)
+ params = {
+ 'account_token': "",
+ 'documents': doc_list,
+ 'options': {
+ 'color': self.env.user.company_id.snailmail_color,
+ 'duplex': self.env.user.company_id.snailmail_duplex,
+ 'currency_name': 'EUR',
+ },
+ }
+ req = jsonrpc(endpoint + '/iap/snailmail/1/estimate', params=params)
+ return req['total_cost']
+
@api.model
def _snailmail_cron(self):
- letters_send = self.search([('state', '=', 'pending')])
- if letters_send:
- letters_send._snailmail_print()
- limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=1)
- limit_date_str = datetime.datetime.strftime(limit_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
- letters_canceled = self.search([
+ letters_send = self.search([
'|',
- ('state', '=', 'canceled'),
- '&',
- ('state' ,'=', 'draft'),
- ('write_date', '<', limit_date_str),
+ ('state', '=', 'pending'),
+ '&',
+ ('state', '=', 'error'),
+ ('error_code', 'in', ['TRIAL_ERROR', 'CREDIT_ERROR', 'ATTACHMENT_ERROR', 'MISSING_REQUIRED_FIELDS'])
])
- letters_canceled.unlink()
+ letters_send._snailmail_print()
+
+ @api.model
+ def fetch_failed_letters(self):
+ failed_letters = self.search([('state', '=', 'error'), ('user_id.id', '=', self.env.user.id), ('res_id', '!=', 0), ('model', '!=', False)])
+ return failed_letters._format_snailmail_failures()
+
+ @api.model
+ def _is_valid_address(self, record):
+ record.ensure_one()
+ required_keys = ['street', 'city', 'zip', 'country_id']
+ return all(record[key] for key in required_keys)
+
+ @api.multi
+ def _format_snailmail_failures(self):
+ """
+ A shorter message to notify a failure update
+ """
+ failures_infos = []
+ for letter in self:
+ info = {
+ 'message_id': letter.message_id.id,
+ 'record_name': letter.message_id.record_name,
+ 'model_name': self.env['ir.model']._get(letter.model).display_name,
+ 'uuid': letter.message_id.message_id,
+ 'res_id': letter.res_id,
+ 'model': letter.model,
+ 'last_message_date': letter.message_id.date,
+ 'module_icon': '/snailmail/static/img/snailmail_failure.png',
+ 'snailmail_status': letter.error_code if letter.state == 'error' else '',
+ 'snailmail_error': letter.state == 'error',
+ 'failure_type': 'snailmail',
+ }
+ failures_infos.append(info)
+ return failures_infos
diff --git a/addons/snailmail/static/img/snailmail_failure.png b/addons/snailmail/static/img/snailmail_failure.png
new file mode 100644
index 00000000000..df591cd75c3
Binary files /dev/null and b/addons/snailmail/static/img/snailmail_failure.png differ
diff --git a/addons/snailmail/static/src/js/mail_failure.js b/addons/snailmail/static/src/js/mail_failure.js
new file mode 100644
index 00000000000..cb363051392
--- /dev/null
+++ b/addons/snailmail/static/src/js/mail_failure.js
@@ -0,0 +1,29 @@
+odoo.define('snailmail.model.MailFailure', function (require) {
+"use strict";
+
+var MailFailure = require('mail.model.MailFailure');
+var core = require('web.core');
+var _t = core._t;
+
+MailFailure.include({
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ getPreview: function () {
+ var preview = this._super.apply(this, arguments);
+ if (this._failureType === 'snailmail') {
+ _.extend(preview, {
+ body: _t("An error occured when sending a letter with Snailmail"),
+ id: 'snailmail_failure',
+ });
+ }
+ return preview;
+ },
+});
+
+});
diff --git a/addons/snailmail/static/src/js/message.js b/addons/snailmail/static/src/js/message.js
new file mode 100644
index 00000000000..b7b74e9242f
--- /dev/null
+++ b/addons/snailmail/static/src/js/message.js
@@ -0,0 +1,85 @@
+odoo.define('snailmail.model.Message', function (require) {
+"use strict";
+
+var Message = require('mail.model.Message');
+
+Message.include({
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Cancels the 'snailmail.letter' which has a message_id corresponding to
+ * the ID of this message, then update the status of the message
+ *
+ * @returns {Deferred}
+ */
+ cancelLetter: function () {
+ var self = this;
+ var mailBus = this.call('mail_service', 'getMailBus');
+ return this._rpc({
+ model: 'mail.message',
+ method: 'cancel_letter',
+ args: [[this.getID()], []],
+ }).then(function () {
+ self.setSnailmailStatus('canceled');
+ self.setSnailmailError(false);
+ mailBus.trigger('update_message', self);
+ });
+ },
+ /**
+ * @return {string}
+ */
+ getSnailmailStatus: function () {
+ return this._snailmailStatus.toLowerCase();
+ },
+ /**
+ * Is the sent snailmail letter in error
+ *
+ * @return {Boolean}
+ */
+ getSnailmailError: function () {
+ return this._snailmailError;
+ },
+ /**
+ * Retries to send the 'snailmail.letter' corresponding to this message
+ *
+ * @returns {Deferred}
+ */
+ resendLetter: function () {
+ return this._rpc({
+ model: 'mail.message',
+ method: 'send_letter',
+ args: [[this.getID()], []],
+ });
+ },
+ /**
+ * @param {string} snailmailStatus
+ */
+ setSnailmailStatus: function (snailmailStatus) {
+ this._snailmailStatus = snailmailStatus;
+ },
+ /**
+ * @param {Boolean} snailmailError
+ */
+ setSnailmailError: function (snailmailError) {
+ this._snailmailError = snailmailError;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @override
+ */
+ _setInitialData: function (data){
+ this._super.apply(this, arguments);
+ this._snailmailStatus = data.snailmail_status;
+ this._snailmailError = data.snailmail_error;
+ },
+});
+
+});
diff --git a/addons/snailmail/static/src/js/snailmail_notification_manager.js b/addons/snailmail/static/src/js/snailmail_notification_manager.js
new file mode 100644
index 00000000000..4d33ba89127
--- /dev/null
+++ b/addons/snailmail/static/src/js/snailmail_notification_manager.js
@@ -0,0 +1,67 @@
+odoo.define('snailmail.NotificationManager', function (require) {
+"use strict";
+
+var MailManager = require('mail.Manager');
+var MailFailure = require('mail.model.MailFailure');
+
+MailManager.include({
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @param {Object} data structure depending on the type
+ * @param {integer} data.id
+ */
+ _handlePartnerNotification: function (data) {
+ if (data.type === 'snailmail_update') {
+ this._handleSnailmailUpdateNotification(data);
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+ /**
+ * Updates message in thread and systray when there's an update in a snailmail letter
+ *
+ * @private
+ * @param {Object} datas
+ * @param {Object[]} datas.elements list of snailmail failure data
+ * @param {string} datas.elements[].message_id ID of related message that
+ * has a snailmail failure.
+ * @param {boolean} datas.elements[].snailmail_error letter is in error
+ * @param {string} datas.elements[].snailmail_status status of the letter
+ */
+ _handleSnailmailUpdateNotification: function (datas) {
+ var self = this;
+ _.each(datas.elements, function (data) {
+ var isNewFailure = data.snailmail_error;
+ var matchedFailure = _.find(self._mailFailures, function (failure) {
+ return failure.getMessageID() === data.message_id;
+ });
+
+ if (matchedFailure) {
+ var index = _.findIndex(self._mailFailures, matchedFailure);
+ if (isNewFailure) {
+ self._mailFailures[index] = new MailFailure(self, data);
+ } else {
+ self._mailFailures.splice(index, 1);
+ }
+ } else if (isNewFailure) {
+ self._mailFailures.push(new MailFailure(self, data));
+ }
+ var message = _.find(self._messages, function (msg) {
+ return msg.getID() === data.message_id;
+ });
+ if (message) {
+ message.setSnailmailStatus(data.snailmail_status);
+ message.setSnailmailError(data.snailmail_error);
+ self._mailBus.trigger('update_message', message);
+ }
+ });
+ this._mailBus.trigger('update_needaction', this.needactionCounter);
+ },
+});
+
+});
diff --git a/addons/snailmail/static/src/js/systray_messaging_menu.js b/addons/snailmail/static/src/js/systray_messaging_menu.js
new file mode 100644
index 00000000000..2265df23ca0
--- /dev/null
+++ b/addons/snailmail/static/src/js/systray_messaging_menu.js
@@ -0,0 +1,78 @@
+odoo.define('snailmail.systray.MessagingMenu', function (require) {
+"use strict";
+
+var MessagingMenu = require('mail.systray.MessagingMenu');
+
+MessagingMenu.include({
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when clicking on a preview related to a snailmail failure
+ *
+ * @private
+ * @param {$.Element} $target DOM of preview element clicked
+ */
+ _clickSnailmailFailurePreview: function ($target) {
+ var documentID = $target.data('document-id');
+ var documentModel = $target.data('document-model');
+ if (documentModel && documentID) {
+ this._openDocument(documentModel, documentID);
+ } else if (documentModel !== 'mail.channel') {
+ // preview of mail failures grouped to different document of same model
+ this.do_action({
+ name: "Snailmail failures",
+ type: 'ir.actions.act_window',
+ view_mode: 'kanban,list,form',
+ views: [[false, 'kanban'], [false, 'list'], [false, 'form']],
+ target: 'current',
+ res_model: documentModel,
+ domain: [['message_ids.snailmail_error', '=', true]],
+ });
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @override
+ */
+ _onClickPreview: function (ev) {
+ var $target = $(ev.currentTarget);
+ var previewID = $target.data('preview-id');
+
+ if (previewID === 'snailmail_failure') {
+ this._clickSnailmailFailurePreview($target);
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+ /**
+ * @private
+ * @override
+ */
+ _onClickPreviewMarkAsRead: function (ev) {
+ ev.stopPropagation();
+ var $preview = $(ev.currentTarget).closest('.o_mail_preview');
+ var previewID = $preview.data('preview-id');
+ if (previewID === 'snailmail_failure') {
+ var documentModel = $preview.data('document-model');
+ var unreadCounter = $preview.data('unread-counter');
+ this.do_action('snailmail.snailmail_letter_cancel_action', {
+ additional_context: {
+ default_model: documentModel,
+ unread_counter: unreadCounter
+ }
+ });
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+});
+
+});
diff --git a/addons/snailmail/static/src/js/thread_widget.js b/addons/snailmail/static/src/js/thread_widget.js
new file mode 100644
index 00000000000..4fd2521b4c7
--- /dev/null
+++ b/addons/snailmail/static/src/js/thread_widget.js
@@ -0,0 +1,227 @@
+odoo.define('snailmail.widget.Thread', function (require) {
+"use strict";
+
+var ThreadWidget = require('mail.widget.Thread');
+var Dialog = require('web.Dialog');
+
+var core = require('web.core');
+var QWeb = core.qweb;
+var _t = core._t;
+
+ThreadWidget.include({
+ dependencies: ['bus_service'],
+ events: _.extend({}, ThreadWidget.prototype.events, {
+ 'click .o_thread_message_snailmail_missing_required_fields': '_onClickMissingRequiredFields',
+ 'click .o_thread_message_snailmail_credit_error': '_onClickCreditError',
+ 'click .o_thread_message_snailmail_trial_error': '_onClickTrialError',
+ 'click .o_thread_message_snailmail_no_price_available': '_onClickNoPriceAvailable',
+ 'click .o_thread_message_snailmail_format_error': '_onClickFormatError',
+ 'click .o_thread_message_snailmail_unknown_error': '_onClickUnknownError',
+ }),
+ /**
+ * @override
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ this._enabledOptions = _.defaults(this._enabledOptions, {
+ displaySnailmailIcons: true,
+ });
+ this._disabledOptions = _.defaults(this._disabledOptions, {
+ displaySnailmailIcons: false,
+ });
+ },
+ /**
+ * @override
+ */
+ render: function (thread, options) {
+ this._super.apply(this, arguments);
+ var messages = _.clone(thread.getMessages({ domain: options.domain || [] }));
+ this._messages = messages;
+ this._renderMessageSnailmailPopover();
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {integer} messageID
+ * @param {string} content
+ */
+ _openCreditErrorDialog: function (messageID, content) {
+ var message = _.find(this._messages, function (message) {
+ return message.getID() === messageID;
+ });
+ new Dialog(this, {
+ size: 'medium',
+ title: _t("Failed letter"),
+ $content: $('
The letter could not be sent due to insufficient credits on your IAP account.
' + + 'The country to which you want to send the letter is not supported by our service.
'); + this._openGenericErrorDialog(messageID, content); + }, + /** + * @private + * @param {MouseEvent} event + */ + _onClickTrialError: function (ev) { + var self = this; + var messageID = $(ev.currentTarget).data('message-id'); + this._rpc({ + model: 'iap.account', + method: 'get_credits_url', + args: ['snailmail', '', 0, true], + }).then(function (link) { + var content = _.str.sprintf(_t( + 'You need credits on your IAP account to send a letter.
' + + 'An unknown error occured. Please contact our support for further assistance.
'); + this._openGenericErrorDialog(messageID, content); + }, +}); + +}); diff --git a/addons/snailmail/static/src/scss/thread.scss b/addons/snailmail/static/src/scss/thread.scss new file mode 100644 index 00000000000..ecf42cef033 --- /dev/null +++ b/addons/snailmail/static/src/scss/thread.scss @@ -0,0 +1,17 @@ +.o_thread_message_snailmail { + padding: 4px; + + &:not(.o_thread_message_snailmail_error) { + color: grey; + } + + &.o_thread_message_snailmail_error { + color: red; + opacity: 1; + cursor: pointer; + } +} + +.o_thread_tooltip_snailmail_icon { + min-width: 1rem; +} diff --git a/addons/snailmail/static/src/xml/thread.xml b/addons/snailmail/static/src/xml/thread.xml new file mode 100644 index 00000000000..aeda07445be --- /dev/null +++ b/addons/snailmail/static/src/xml/thread.xml @@ -0,0 +1,37 @@ + + + +Test Post Performances with an inbox ping
', partner_ids=self.user_test.partner_id.ids,