[IMP] mail_tracking: Failed Messages to 12.0

This commit is contained in:
Alexandre Díaz 2019-10-17 03:28:42 +02:00 committed by Jasmin Solanki
parent 5ec699ab9a
commit eba1e0de8a
35 changed files with 2139 additions and 1022 deletions

View File

@ -52,44 +52,28 @@ status icon will appear just right to name of notified partner.
These are all available status icons:
<<<<<<< HEAD
.. |sent| image:: mail_tracking/static/src/img/sent.png
.. |sent| image:: ../static/src/img/sent.png
:width: 10px
.. |delivered| image:: mail_tracking/static/src/img/delivered.png
.. |delivered| image:: ../static/src/img/delivered.png
:width: 15px
.. |opened| image:: mail_tracking/static/src/img/opened.png
.. |opened| image:: ../static/src/img/opened.png
:width: 15px
.. |error| image:: mail_tracking/static/src/img/error.png
.. |error| image:: ../static/src/img/error.png
:width: 10px
.. |waiting| image:: mail_tracking/static/src/img/waiting.png
.. |waiting| image:: ../static/src/img/waiting.png
:width: 10px
.. |unknown| image:: mail_tracking/static/src/img/unknown.png
=======
.. |sent| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/sent.png
.. |unknown| image:: ../static/src/img/unknown.png
:width: 10px
.. |delivered| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/delivered.png
:width: 15px
.. |opened| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/opened.png
:width: 15px
.. |error| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/error.png
.. |cc| image:: ../static/src/img/cc.png
:width: 10px
.. |waiting| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/waiting.png
:width: 10px
.. |unknown| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/unknown.png
>>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View)
:width: 10px
.. |cc| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/cc.png
.. |noemail| image:: ../static/src/img/no_email.png
:width: 10px
|unknown| **Unknown**: No email tracking info available. Maybe this notified partner has 'Receive Inbox Notifications by Email' == 'Never'
@ -106,34 +90,37 @@ These are all available status icons:
|cc| **Cc**: It's a Carbon-Copy recipient. Can't know the status so is 'Unknown'
|noemail| **No Email**: The partner doesn't have a defined email
<<<<<<< HEAD
If you want to see all tracking emails and events you can go to
* Settings > Technical > Email > Tracking emails
* Settings > Technical > Email > Tracking events
=======
When the message generates and 'error' status, it will apear on discuss 'Failed'
channel. Any view that uses 'mail_thread' widget can show the failed messages
When the message generates an 'error' status, it will apear on discuss 'Failed'
channel. Any view with chatter can show the failed messages
too.
* Discuss
.. image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_discuss.png
.. image:: https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_discuss.png
* Chatter
.. image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_widget.png
.. image:: https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_widget.png
You can use "Failed sent messages" filter present in all views to show records
with messages in failed status and that needs an user action.
* Filter
.. image:: https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_filter.png
Known issues / Roadmap
======================
* Handle message updates on discuss 'channel_failed' instead of showing the
'outdated' message.
* Adapt chat_manager changes in v12
* Adapt discuss changes in v12
* Add pivot for tracking events and mail trackings
>>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View)
Bug Tracker
===========

View File

@ -28,12 +28,12 @@
"views/mail_tracking_event_view.xml",
"views/mail_message_view.xml",
"views/res_partner_view.xml",
"wizard/mail_compose_message_view.xml",
],
"qweb": [
"static/src/xml/mail_tracking.xml",
"static/src/xml/failed_message.xml",
"static/src/xml/client_action.xml",
"static/src/xml/failed_message/common.xml",
"static/src/xml/failed_message/thread.xml",
"static/src/xml/failed_message/discuss.xml",
],
'demo': [
'demo/demo.xml',

View File

@ -2,10 +2,11 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import werkzeug
from psycopg2 import OperationalError
from odoo import api, http, registry, SUPERUSER_ID, _
import odoo
from contextlib import contextmanager
from odoo import api, http, SUPERUSER_ID
from odoo.addons.mail.controllers.main import MailController
from odoo.http import request
import logging
import base64
_logger = logging.getLogger(__name__)
@ -13,35 +14,23 @@ _logger = logging.getLogger(__name__)
BLANK = 'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
def _env_get(db, callback, tracking_id, event_type, **kw):
res = 'NOT FOUND'
reg = False
current = http.request.db and db == http.request.db
env = current and http.request.env
if not env:
with api.Environment.manage():
try:
reg = registry(db)
except OperationalError:
_logger.warning("Selected BD '%s' not found", db)
except Exception: # pragma: no cover
_logger.warning("Selected BD '%s' connection error", db)
if reg:
_logger.info("New environment for database '%s'", db)
with reg.cursor() as new_cr:
new_env = api.Environment(new_cr, SUPERUSER_ID, {})
res = callback(new_env, tracking_id, event_type, **kw)
new_env.cr.commit()
else:
# make sudo when reusing environment
env = env(user=SUPERUSER_ID)
res = callback(env, tracking_id, event_type, **kw)
return res
@contextmanager
def db_env(dbname):
if not http.db_filter([dbname]):
raise werkzeug.exceptions.BadRequest()
cr = None
if dbname == http.request.db:
cr = http.request.cr
if not cr:
cr = odoo.sql_db.db_connect(dbname).cursor()
with api.Environment.manage():
yield api.Environment(cr, SUPERUSER_ID, {})
class MailTrackingController(MailController):
def _request_metadata(self):
"""Prepare remote info metadata"""
request = http.request.httprequest
return {
'ip': request.remote_addr or False,
@ -50,37 +39,45 @@ class MailTrackingController(MailController):
'ua_family': request.user_agent.browser or False,
}
def _tracking_open(self, env, tracking_id, event_type, **kw):
tracking_email = env['mail.tracking.email'].search([
('id', '=', tracking_id),
])
if tracking_email:
metadata = self._request_metadata()
tracking_email.event_create('open', metadata)
else:
_logger.warning(
"MailTracking email '%s' not found", tracking_id)
def _tracking_event(self, env, tracking_id, event_type, **kw):
@http.route(['/mail/tracking/all/<string:db>',
'/mail/tracking/event/<string:db>/<string:event_type>'],
type='http', auth='none', csrf=False)
def mail_tracking_event(self, db, event_type=None, **kw):
"""Route used by external mail service"""
metadata = self._request_metadata()
return env['mail.tracking.email'].event_process(
http.request, kw, metadata, event_type=event_type)
res = None
with db_env(db) as env:
try:
res = env['mail.tracking.email'].event_process(
http.request, kw, metadata, event_type=event_type)
except Exception:
pass
if not res or res == 'NOT FOUND':
return werkzeug.exceptions.NotAcceptable()
return res
@http.route('/mail/tracking/all/<string:db>',
type='http', auth='none', csrf=False)
def mail_tracking_all(self, db, **kw):
return _env_get(db, self._tracking_event, None, None, **kw)
@http.route('/mail/tracking/event/<string:db>/<string:event_type>',
type='http', auth='none', csrf=False)
def mail_tracking_event(self, db, event_type, **kw):
return _env_get(db, self._tracking_event, None, event_type, **kw)
@http.route('/mail/tracking/open/<string:db>'
'/<int:tracking_email_id>/blank.gif',
type='http', auth='none')
def mail_tracking_open(self, db, tracking_email_id, **kw):
_env_get(db, self._tracking_open, tracking_email_id, None, **kw)
@http.route(['/mail/tracking/open/<string:db>'
'/<int:tracking_email_id>/blank.gif',
'/mail/tracking/open/<string:db>'
'/<int:tracking_email_id>/<string:token>/blank.gif'],
type='http', auth='none', methods=['GET'])
def mail_tracking_open(self, db, tracking_email_id, token=False, **kw):
"""Route used to track mail openned (With & Without Token)"""
metadata = self._request_metadata()
with db_env(db) as env:
try:
tracking_email = env['mail.tracking.email'].search([
('id', '=', tracking_email_id),
('state', 'in', ['sent', 'delivered']),
('token', '=', token),
])
if tracking_email:
tracking_email.event_create('open', metadata)
else:
_logger.warning(
"MailTracking email '%s' not found", tracking_email_id)
except Exception:
pass
# Always return GIF blank image
response = werkzeug.wrappers.Response()
@ -89,20 +86,11 @@ class MailTrackingController(MailController):
return response
@http.route()
def mail_client_action(self):
values = super().mail_client_action()
values['channel_slots']['channel_channel'].append({
'id': 'channel_failed',
'name': _("Failed"),
'uuid': None,
'state': 'open',
'is_minimized': False,
'channel_type': 'static',
'public': False,
'mass_mailing': None,
'group_based_subscription': None,
})
def mail_init_messaging(self):
"""Route used to initial values of Discuss app"""
values = super().mail_init_messaging()
values.update({
'failed_counter': request.env['mail.message'].get_failed_count(),
'failed_counter':
http.request.env['mail.message'].get_failed_count(),
})
return values

View File

@ -2,6 +2,31 @@
<odoo>
<data noupdate="0">
<!-- Message with CC -->
<record id="mail_message_cc" model="mail.message">
<field name="model">res.partner</field>
<field name="res_id" ref="base.partner_demo" />
<field name="message_type">comment</field>
<field name="subtype_id" ref="mail.mt_comment" />
<field name="email_cc">acc@testmail.com,wood.corner26@example.com,toni.rhodes11@example.com</field>
<field name="mail_tracking_needs_action">1</field>
<field name="body"><![CDATA[<p>This is a message with CC</p>]]></field>
<field name="email_from">wood.corner26@example.com</field>
<field name="author_id" ref="base.res_partner_1" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field name="subject">Message with CC</field>
</record>
<record id="mail_tracking_email_cc" model="mail.tracking.email">
<field name="name">Message with CC</field>
<field name="mail_message_id" ref="mail_message_cc" />
<field name="partner_id" ref="base.res_partner_1" />
<field name="recipient">demo@yourcompany.example.com</field>
<field name="sender">wood.corner26@example.com</field>
<field name="state">sent</field>
<field name="time" eval="DateTime.today().strftime('%Y-%m-%d %H:%M')"/>
</record>
<!-- Failed Message A -->
<record id="mail_message_failed" model="mail.message">
<field name="model">res.partner</field>
@ -10,7 +35,7 @@
<field name="subtype_id" ref="mail.mt_comment" />
<field name="mail_tracking_needs_action">1</field>
<field name="body"><![CDATA[<p>This is a failed message</p>]]></field>
<field name="email_from">res1@yourcompany.example.com</field>
<field name="email_from">wood.corner26@example.com</field>
<field name="author_id" ref="base.res_partner_1" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field name="subject">Failed Message</field>
@ -20,8 +45,8 @@
<field name="name">Failed Message</field>
<field name="mail_message_id" ref="mail_message_failed" />
<field name="partner_id" ref="base.res_partner_1" />
<field name="recipient">res1@yourcompany.example.com</field>
<field name="sender">demo@yourcompany.example.com</field>
<field name="recipient">demo@yourcompany.example.com</field>
<field name="sender">wood.corner26@example.com</field>
<field name="state">error</field>
<field name="time" eval="DateTime.today().strftime('%Y-%m-%d %H:%M')"/>
</record>
@ -34,7 +59,7 @@
<field name="subtype_id" ref="mail.mt_comment" />
<field name="mail_tracking_needs_action">1</field>
<field name="body"><![CDATA[<p>This is another failed message</p>]]></field>
<field name="email_from">res10@yourcompany.example.com</field>
<field name="email_from">jackson.group82@example.com</field>
<field name="author_id" ref="base.res_partner_10" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field name="subject">Failed Message</field>
@ -44,8 +69,32 @@
<field name="name">Failed Message</field>
<field name="mail_message_id" ref="mail_message_failed_b" />
<field name="partner_id" ref="base.res_partner_10" />
<field name="recipient">res10@yourcompany.example.com</field>
<field name="sender">demo@yourcompany.example.com</field>
<field name="recipient">demo@yourcompany.example.com</field>
<field name="sender">jackson.group82@example.com</field>
<field name="state">error</field>
<field name="time" eval="DateTime.today().strftime('%Y-%m-%d %H:%M')"/>
</record>
<!-- Failed Message C -->
<record id="mail_message_failed_c" model="mail.message">
<field name="model">res.partner</field>
<field name="res_id" ref="base.partner_demo" />
<field name="message_type">comment</field>
<field name="subtype_id" ref="mail.mt_comment" />
<field name="mail_tracking_needs_action">1</field>
<field name="body"><![CDATA[<p>This is another failed message</p>]]></field>
<field name="email_from">admin@example.com</field>
<field name="author_id" ref="base.partner_admin" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field name="subject">Failed Message</field>
</record>
<record id="mail_tracking_email_failed_c" model="mail.tracking.email">
<field name="name">Failed Message</field>
<field name="mail_message_id" ref="mail_message_failed_c" />
<field name="partner_id" ref="base.partner_admin" />
<field name="recipient">demo@yourcompany.example.com</field>
<field name="sender">admin@example.com</field>
<field name="state">error</field>
<field name="time" eval="DateTime.today().strftime('%Y-%m-%d %H:%M')"/>
</record>

View File

@ -1,23 +1,21 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mail_tracking
# * mail_tracking
#
# Translators:
# OCA Transbot <transbot@odoo-community.org>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 02:19+0000\n"
"PO-Revision-Date: 2019-08-04 17:44+0000\n"
"Last-Translator: eduardgm <eduard.garcia@qubiq.es>\n"
"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n"
"POT-Creation-Date: 2019-11-12 17:22+0000\n"
"PO-Revision-Date: 2019-11-12 18:26+0100\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.7.1\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n"
"X-Generator: Poedit 2.2.4\n"
#. module: mail_tracking
#: model:ir.model.fields,help:mail_tracking.field_mail_tracking_email__state
@ -43,11 +41,18 @@ msgid ""
"recipient Mail Exchange (MX) server.\n"
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:189
#, python-format
msgid "-Unknown Author-"
msgstr "-Autor Desconocido-"
#. module: mail_tracking
#: model:ir.model.fields,help:mail_tracking.field_mail_compose_message__email_cc
#: model:ir.model.fields,help:mail_tracking.field_mail_message__email_cc
msgid "Additional recipients that receive a \"Carbon Copy\" of the e-mail"
msgstr ""
"Destinatarios adicionales que reciben una \"Copia de Carbón\" del correo"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
@ -70,8 +75,8 @@ msgid "Bounced"
msgstr "Rebotado"
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_thread.py:39
#: code:addons/mail_tracking/models/mail_thread.py:43
#: code:addons/mail_tracking/models/mail_thread.py:64
#: code:addons/mail_tracking/models/mail_thread.py:68
#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__email_cc
#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__email_cc
#, python-format
@ -93,6 +98,20 @@ msgstr "Clicado"
msgid "Clicked URL"
msgstr "URL Clicada"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:22
#, python-format
msgid "Congratulations, you don't have any failed messages"
msgstr "¡Enhorabuena! No tienes mensajes fallidos"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/js/failed_message/discuss.js:231
#, python-format
msgid "Congratulations, your failed mailbox is empty"
msgstr "¡Enhorabuena! tu buzón de fallidos está vacio"
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_res_partner
msgid "Contact"
@ -128,21 +147,23 @@ msgstr "Fecha"
#. module: mail_tracking
#: selection:mail.tracking.event,event_type:0
msgid "Deferral"
msgstr ""
msgstr "Aplazamiento"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: selection:mail.tracking.email,state:0
msgid "Deferred"
msgstr ""
msgstr "Diferido"
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:74
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
#: selection:mail.tracking.email,state:0
#: selection:mail.tracking.event,event_type:0
#, python-format
msgid "Delivered"
msgstr ""
msgstr "Entregado"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_bounced_mixin__display_name
@ -165,94 +186,151 @@ msgstr "Correo electrónico"
#: model:ir.model.fields,field_description:mail_tracking.field_mail_bounced_mixin__email_bounced
#: model:ir.model.fields,field_description:mail_tracking.field_res_partner__email_bounced
#: model:ir.model.fields,field_description:mail_tracking.field_res_users__email_bounced
#, fuzzy
msgid "Email Bounced"
msgstr "E mail rebotado"
msgstr "Correo rebotado"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_res_partner__email_score
#: model:ir.model.fields,field_description:mail_tracking.field_res_users__email_score
#, fuzzy
msgid "Email Score"
msgstr "Reputación del email"
msgstr "Reputación del correo"
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_mail_thread
#, fuzzy
msgid "Email Thread"
msgstr "Reputación del email"
msgstr "Hilo de mensajes"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_res_partner_filter
msgid "Email bounced"
msgstr "E mail rebotado"
msgstr "Email rebotado"
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_mail_resend_message
msgid "Email resend wizard"
msgstr "Asistente de reenvio de correo electrónico"
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:73
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_form
#: selection:mail.tracking.email,state:0
#, python-format
msgid "Error"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__error_smtp_server
msgid "Error SMTP server"
msgstr ""
msgstr "Error servidor SMTP"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__error_description
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__error_description
msgid "Error description"
msgstr ""
msgstr "Descripción del error"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__error_details
msgid "Error details"
msgstr ""
msgstr "Detalles del error"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__error_type
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__error_type
msgid "Error type"
msgstr ""
msgstr "Tipo de error"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__event_type
msgid "Event type"
msgstr ""
msgstr "Tipo de evento"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/js/failed_message/discuss.js:350
#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:13
#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:21
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
#, python-format
msgid "Failed"
msgstr ""
msgstr "Fallido"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_account_analytic_account__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_calendar_event__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_hr_department__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_hr_employee__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_hr_job__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_mail_blacklist__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_mail_channel__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_mail_thread__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_res_partner__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_res_users__failed_message_ids
msgid "Failed Messages"
msgstr "Mensajes Fallidos"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:39
#, python-format
msgid "Failed Recipients:"
msgstr "Destinatarios Fallidos:"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:7
#, python-format
msgid "Failed messages"
msgstr "Mensajes fallidos"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:23
#, python-format
msgid "Failed messages appear here."
msgstr "Los mensajes fallidos se muestran aquí."
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_thread.py:92
#, python-format
msgid "Failed sent messages"
msgstr "Mensajes enviados fallidos"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
msgid "Group By"
msgstr ""
msgstr "Agrupar por"
#. module: mail_tracking
#: selection:mail.tracking.event,event_type:0
msgid "Hard bounce"
msgstr ""
msgstr "Rebote duro"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_bounced_mixin__id
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__id
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__id
msgid "ID"
msgstr "ID"
msgstr ""
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
msgid "IP"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__is_failed_message
#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__is_failed_message
#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__is_failed_message
msgid "Is Failed Message"
msgstr "Es un Mensajes Fallido"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__mobile
msgid "Is mobile?"
msgstr ""
msgstr "Es móvil?"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_bounced_mixin____last_update
@ -275,49 +353,68 @@ msgstr "Última actualización en"
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_ir_mail_server
#, fuzzy
msgid "Mail Server"
msgstr "ir.mail_server"
msgstr "Servidor de correo"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__mail_tracking_needs_action
#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__mail_tracking_needs_action
#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__mail_tracking_needs_action
msgid "Mail Tracking Needs Action"
msgstr "Mail Tracking necesita acción"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__mail_tracking_ids
#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__mail_tracking_ids
#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__mail_tracking_ids
msgid "Mail Trackings"
msgstr ""
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_mail_bounced_mixin
#, fuzzy
msgid "Mail bounced mixin"
msgstr "E mail rebotado"
msgstr "Correo rebotado mixin"
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_mail_tracking_email
msgid "MailTracking email"
msgstr "MailTracking email"
msgstr "MailTracking correo"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
msgid "MailTracking email search"
msgstr ""
msgstr "MailTracking búsqueda de correo"
#. module: mail_tracking
#: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_email
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_tree
msgid "MailTracking emails"
msgstr ""
msgstr "MailTracking correos"
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_mail_tracking_event
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_form
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_form
msgid "MailTracking event"
msgstr "MailTracking event"
msgstr "MailTracking evento"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
msgid "MailTracking event search"
msgstr ""
msgstr "MailTracking búsqueda de eventos"
#. module: mail_tracking
#: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_event
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_tree
msgid "MailTracking events"
msgstr ""
msgstr "MailTracking eventos"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:30
#, python-format
msgid "Mark all as reviewed"
msgstr "Marcar todos como revisado"
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_mail_message
@ -329,10 +426,10 @@ msgstr "Mensaje"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/js/mail_tracking.js:135
#: code:addons/mail_tracking/static/src/js/mail_tracking.js:147
#, python-format
msgid "Message tracking"
msgstr ""
msgstr "Seguimiento del mensaje"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
@ -351,17 +448,19 @@ msgstr "SO"
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
#: selection:mail.tracking.event,event_type:0
msgid "Open"
msgstr ""
msgstr "Abrir"
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:75
#: selection:mail.tracking.email,state:0
#, python-format
msgid "Opened"
msgstr ""
msgstr "Abierto"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__os_family
msgid "Operating system family"
msgstr ""
msgstr "Familia del sistema operativo"
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_mail_mail
@ -379,83 +478,127 @@ msgstr "Empresa"
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_tree
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
msgid "Recipient"
msgstr ""
msgstr "Destinatario"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__recipient
msgid "Recipient email"
msgstr ""
msgstr "Correo del destinatario"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__recipient_address
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__recipient_address
msgid "Recipient email address"
msgstr ""
msgstr "Dirección de correo de destinatario"
#. module: mail_tracking
#: selection:mail.tracking.email,state:0
#: selection:mail.tracking.event,event_type:0
msgid "Rejected"
msgstr ""
msgstr "Rechazado"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/common.xml:10
#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:35
#, python-format
msgid "Retry"
msgstr "Reintentar"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__smtp_server
msgid "SMTP server"
msgstr ""
msgstr "Servidor SMTP"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__token
msgid "Security Token"
msgstr "Token de seguridad"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_tree
msgid "Sender"
msgstr ""
msgstr "Remitente"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__sender
msgid "Sender email"
msgstr ""
msgstr "Correo del remitente"
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:74
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
#: selection:mail.tracking.email,state:0
#: selection:mail.tracking.event,event_type:0
#, python-format
msgid "Sent"
msgstr ""
msgstr "Enviado"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:30
#, python-format
msgid "Set all as reviewed"
msgstr "Marcar todos como revisados"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/common.xml:7
#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:32
#, python-format
msgid "Set as Reviewed"
msgstr "Marcar como Revisado"
#. module: mail_tracking
#: selection:mail.tracking.event,event_type:0
msgid "Soft bounce"
msgstr ""
msgstr "Rebote débil"
#. module: mail_tracking
#: selection:mail.tracking.email,state:0
msgid "Soft bounced"
msgstr ""
msgstr "Rebotado débil"
#. module: mail_tracking
#: selection:mail.tracking.email,state:0
#: selection:mail.tracking.event,event_type:0
msgid "Spam"
msgstr ""
msgstr "No deseado"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__state
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
msgid "State"
msgstr ""
msgstr "Estado"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:96
#: code:addons/mail_tracking/models/mail_message.py:76
#, python-format
msgid "Status: unknown"
msgstr ""
msgid "Status: %s"
msgstr "Estado: %s"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__name
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
msgid "Subject"
msgstr "Asunto"
#. module: mail_tracking
#: model:ir.model.fields,help:mail_tracking.field_mail_compose_message__mail_tracking_needs_action
#: model:ir.model.fields,help:mail_tracking.field_mail_mail__mail_tracking_needs_action
#: model:ir.model.fields,help:mail_tracking.field_mail_message__mail_tracking_needs_action
msgid "The message tracking will be considered to filter tracking issues"
msgstr ""
"El seguimiento del correo puede ser considerado para filtrar incidencia de "
"seguimiento"
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:82
#, python-format
msgid "The partner doesn't have a defined email"
msgstr "El socio no tiene un correo definido"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__time
@ -463,27 +606,26 @@ msgstr ""
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
msgid "Time"
msgstr ""
msgstr "Tiempo"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:53
#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:54
#, python-format
msgid "To:"
msgstr ""
msgstr "Para:"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_res_partner__tracking_emails_count
#: model:ir.model.fields,field_description:mail_tracking.field_res_users__tracking_emails_count
#, fuzzy
msgid "Tracking Emails Count"
msgstr "MailTracking email"
msgstr "Contador de correos con seguimiento"
#. module: mail_tracking
#: model:ir.ui.menu,name:mail_tracking.menu_mail_tracking_email
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_partner_form
msgid "Tracking emails"
msgstr ""
msgstr "Correos con seguimiento"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__tracking_event_ids
@ -494,15 +636,15 @@ msgstr "Eventos de seguimiento"
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/js/mail_tracking.js:115
#: code:addons/mail_tracking/static/src/js/mail_tracking.js:127
#, python-format
msgid "Tracking partner"
msgstr ""
msgstr "Socio del seguimiento"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
msgid "Type"
msgstr ""
msgstr "Tipo"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
@ -515,22 +657,28 @@ msgstr ""
msgid "UTC timestamp"
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:75
#, python-format
msgid "Unknown"
msgstr "Desconocido"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
msgid "Unsubscribe"
msgstr ""
msgstr "Darse de baja"
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: selection:mail.tracking.email,state:0
#: selection:mail.tracking.event,event_type:0
msgid "Unsubscribed"
msgstr ""
msgstr "Dado de baja"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__ip
msgid "User IP"
msgstr ""
msgstr "IP del usuario"
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__user_agent
@ -554,4 +702,10 @@ msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__user_country_id
msgid "User country"
msgstr ""
msgstr "País del Usuario"
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:73
#, python-format
msgid "Waiting"
msgstr "Esperando"

View File

@ -27,6 +27,12 @@ msgid " * The 'Error' status indicates that there was an error when trying to se
""
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:189
#, python-format
msgid "-Unknown Author-"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,help:mail_tracking.field_mail_compose_message__email_cc
#: model:ir.model.fields,help:mail_tracking.field_mail_message__email_cc
@ -54,8 +60,8 @@ msgid "Bounced"
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_thread.py:39
#: code:addons/mail_tracking/models/mail_thread.py:43
#: code:addons/mail_tracking/models/mail_thread.py:64
#: code:addons/mail_tracking/models/mail_thread.py:68
#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__email_cc
#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__email_cc
#, python-format
@ -77,6 +83,20 @@ msgstr ""
msgid "Clicked URL"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:22
#, python-format
msgid "Congratulations, you don't have any failed messages"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/js/failed_message/discuss.js:231
#, python-format
msgid "Congratulations, your failed mailbox is empty"
msgstr ""
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_res_partner
msgid "Contact"
@ -121,10 +141,12 @@ msgid "Deferred"
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:74
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
#: selection:mail.tracking.email,state:0
#: selection:mail.tracking.event,event_type:0
#, python-format
msgid "Delivered"
msgstr ""
@ -169,8 +191,15 @@ msgid "Email bounced"
msgstr ""
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_mail_resend_message
msgid "Email resend wizard"
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:73
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_form
#: selection:mail.tracking.email,state:0
#, python-format
msgid "Error"
msgstr ""
@ -202,11 +231,54 @@ msgid "Event type"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/js/failed_message/discuss.js:350
#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:13
#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:21
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
#, python-format
msgid "Failed"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_account_analytic_account__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_calendar_event__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_mail_blacklist__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_mail_channel__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_mail_thread__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_res_partner__failed_message_ids
#: model:ir.model.fields,field_description:mail_tracking.field_res_users__failed_message_ids
msgid "Failed Messages"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:39
#, python-format
msgid "Failed Recipients:"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:7
#, python-format
msgid "Failed messages"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:23
#, python-format
msgid "Failed messages appear here."
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_thread.py:92
#, python-format
msgid "Failed sent messages"
msgstr ""
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
@ -230,6 +302,13 @@ msgstr ""
msgid "IP"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__is_failed_message
#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__is_failed_message
#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__is_failed_message
msgid "Is Failed Message"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__mobile
msgid "Is mobile?"
@ -259,6 +338,20 @@ msgstr ""
msgid "Mail Server"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__mail_tracking_needs_action
#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__mail_tracking_needs_action
#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__mail_tracking_needs_action
msgid "Mail Tracking Needs Action"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__mail_tracking_ids
#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__mail_tracking_ids
#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__mail_tracking_ids
msgid "Mail Trackings"
msgstr ""
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_mail_bounced_mixin
msgid "Mail bounced mixin"
@ -298,6 +391,13 @@ msgstr ""
msgid "MailTracking events"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:30
#, python-format
msgid "Mark all as reviewed"
msgstr ""
#. module: mail_tracking
#: model:ir.model,name:mail_tracking.model_mail_message
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__mail_message_id
@ -308,7 +408,7 @@ msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/js/mail_tracking.js:135
#: code:addons/mail_tracking/static/src/js/mail_tracking.js:147
#, python-format
msgid "Message tracking"
msgstr ""
@ -333,7 +433,9 @@ msgid "Open"
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:75
#: selection:mail.tracking.email,state:0
#, python-format
msgid "Opened"
msgstr ""
@ -377,11 +479,24 @@ msgstr ""
msgid "Rejected"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/common.xml:10
#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:35
#, python-format
msgid "Retry"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__smtp_server
msgid "SMTP server"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__token
msgid "Security Token"
msgstr ""
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_tree
@ -394,13 +509,30 @@ msgid "Sender email"
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:74
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
#: selection:mail.tracking.email,state:0
#: selection:mail.tracking.event,event_type:0
#, python-format
msgid "Sent"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:30
#, python-format
msgid "Set all as reviewed"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/failed_message/common.xml:7
#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:32
#, python-format
msgid "Set as Reviewed"
msgstr ""
#. module: mail_tracking
#: selection:mail.tracking.event,event_type:0
msgid "Soft bounce"
@ -424,10 +556,9 @@ msgid "State"
msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:96
#: code:addons/mail_tracking/models/mail_message.py:76
#, python-format
msgid "Status: unknown"
msgid "Status: %s"
msgstr ""
#. module: mail_tracking
@ -436,6 +567,19 @@ msgstr ""
msgid "Subject"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,help:mail_tracking.field_mail_compose_message__mail_tracking_needs_action
#: model:ir.model.fields,help:mail_tracking.field_mail_mail__mail_tracking_needs_action
#: model:ir.model.fields,help:mail_tracking.field_mail_message__mail_tracking_needs_action
msgid "The message tracking will be considered to filter tracking issues"
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:82
#, python-format
msgid "The partner doesn't have a defined email"
msgstr ""
#. module: mail_tracking
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__time
#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__time
@ -446,7 +590,7 @@ msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:53
#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:54
#, python-format
msgid "To:"
msgstr ""
@ -472,7 +616,7 @@ msgstr ""
#. module: mail_tracking
#. openerp-web
#: code:addons/mail_tracking/static/src/js/mail_tracking.js:115
#: code:addons/mail_tracking/static/src/js/mail_tracking.js:127
#, python-format
msgid "Tracking partner"
msgstr ""
@ -493,6 +637,12 @@ msgstr ""
msgid "UTC timestamp"
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:75
#, python-format
msgid "Unknown"
msgstr ""
#. module: mail_tracking
#: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search
msgid "Unsubscribe"
@ -534,3 +684,9 @@ msgstr ""
msgid "User country"
msgstr ""
#. module: mail_tracking
#: code:addons/mail_tracking/models/mail_message.py:73
#, python-format
msgid "Waiting"
msgstr ""

View File

@ -0,0 +1,11 @@
# Copyright 2019 Alexandre Díaz
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from openupgradelib.openupgrade import migrate
@migrate()
def migrate(env, version):
cr = env.cr
cr.execute("UPDATE mail_tracking_email SET token = NULL")

View File

@ -6,6 +6,6 @@ from . import mail_mail
from . import mail_message
from . import mail_tracking_email
from . import mail_tracking_event
from . import mail_composer
from . import res_partner
from . import mail_thread
from . import mail_resend_message

View File

@ -1,12 +1,8 @@
# Copyright 2018 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class MailBouncedMixin(models.AbstractModel):
""" A mixin class to use if you want to add is_bounced flag on a model.

View File

@ -1,30 +0,0 @@
# Copyright 2019 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
class MailComposer(models.TransientModel):
_inherit = 'mail.compose.message'
hide_followers = fields.Boolean(string="Hide follower message",
default=False)
@api.multi
def send_mail(self, auto_commit=False):
""" This method marks as reviewed the message when using the 'Retry'
option in the mail_failed_message widget"""
message = self.env['mail.message'].browse(
self._context.get('message_id'))
if message.exists():
message.mail_tracking_needs_action = False
return super().send_mail(auto_commit=auto_commit)
@api.model
def get_record_data(self, values):
values = super(MailComposer, self).get_record_data(values)
if self._context.get('default_hide_followers', False):
values['partner_ids'] = [
(6, 0, self._context.get('default_partner_ids', list()))
]
return values

View File

@ -12,6 +12,7 @@ class MailMail(models.Model):
_inherit = 'mail.mail'
def _tracking_email_prepare(self, partner, email):
"""Prepare email.tracking.email record values"""
ts = time.time()
dt = datetime.utcfromtimestamp(ts)
email_to_list = email.get('email_to', [])
@ -28,7 +29,9 @@ class MailMail(models.Model):
}
def _send_prepare_values(self, partner=None):
email = super(MailMail, self)._send_prepare_values(partner=partner)
"""Creates the mail.tracking.email record and adds the image tracking
to the email"""
email = super()._send_prepare_values(partner=partner)
vals = self._tracking_email_prepare(partner, email)
tracking_email = self.env['mail.tracking.email'].sudo().create(vals)
return tracking_email.tracking_img_add(email)

View File

@ -22,12 +22,29 @@ class MailMessage(models.Model):
" to filter tracking issues",
default=False,
)
is_failed_message = fields.Boolean(compute="_compute_is_failed_message")
@api.model
def get_failed_states(self):
"""The 'failed' states of the message"""
return {'error', 'rejected', 'spam', 'bounced', 'soft-bounced'}
@api.depends('mail_tracking_needs_action', 'author_id', 'partner_ids',
'mail_tracking_ids.state')
def _compute_is_failed_message(self):
"""Compute 'is_failed_message' field for the active user"""
failed_states = self.get_failed_states()
for message in self:
needs_action = message.mail_tracking_needs_action
involves_me = self.env.user.partner_id in (
message.author_id | message.partner_ids)
has_failed_trackings = failed_states.intersection(
message.mapped("mail_tracking_ids.state"))
message.is_failed_message = bool(
needs_action and involves_me and has_failed_trackings)
def _tracking_status_map_get(self):
"""Map tracking states to be used in chatter"""
return {
'False': 'waiting',
'error': 'error',
@ -43,6 +60,7 @@ class MailMessage(models.Model):
}
def _partner_tracking_status_get(self, tracking_email):
"""Determine tracking status"""
tracking_status_map = self._tracking_status_map_get()
status = 'unknown'
if tracking_email:
@ -51,12 +69,23 @@ class MailMessage(models.Model):
return status
def _partner_tracking_status_human_get(self, status):
"""Translations for tracking statuses to be used on qweb"""
statuses = {'waiting': _('Waiting'), 'error': _('Error'),
'sent': _('Sent'), 'delivered': _('Delivered'),
'opened': _('Opened'), 'unknown': _('Unknown')}
return _("Status: %s") % statuses[status]
@api.model
def _get_error_description(self, tracking):
"""Translations for error descriptions to be used on qweb"""
descriptions = {
'no_recipient': _("The partner doesn't have a defined email"),
}
return descriptions.get(tracking.error_type,
tracking.error_description)
def tracking_status(self):
"""Generates a complete status tracking of the messages by partner"""
res = {}
for message in self:
partner_trackings = []
@ -79,6 +108,9 @@ class MailMessage(models.Model):
'status': status,
'status_human':
self._partner_tracking_status_human_get(status),
'error_type': tracking.error_type,
'error_description':
self._get_error_description(tracking),
'tracking_id': tracking.id,
'recipient': recipient,
'partner_id': tracking.partner_id.id,
@ -94,114 +126,132 @@ class MailMessage(models.Model):
partners |= message.needaction_partner_ids
# Remove recipients already included
partners -= partners_already
tracking_unkown_values = {
'status': 'unknown',
'status_human': self._partner_tracking_status_human_get(
'unknown'),
'error_type': False,
'error_description': False,
'tracking_id': False,
}
for partner in partners:
# If there is partners not included, then status is 'unknown'
# Because can be an Cc recipient
# and perhaps a Cc recipient
isCc = False
if partner.email in email_cc_list:
email_cc_list.discard(partner.email)
isCc = True
partner_trackings.append({
'status': 'unknown',
'status_human':
self._partner_tracking_status_human_get('unknown'),
'tracking_id': False,
tracking_unkown_values.update({
'recipient': partner.name,
'partner_id': partner.id,
'isCc': isCc,
})
partner_trackings.append(tracking_unkown_values.copy())
for email in email_cc_list:
# If there is Cc without partner
partner_trackings.append({
'status': 'unknown',
'status_human':
self._partner_tracking_status_human_get('unknown'),
'tracking_id': False,
tracking_unkown_values.update({
'recipient': email,
'partner_id': False,
'isCc': True,
})
res[message.id] = partner_trackings
partner_trackings.append(tracking_unkown_values.copy())
res[message.id] = {
'partner_trackings': partner_trackings,
'is_failed_message': message.is_failed_message,
}
return res
@api.model
def _message_read_dict_postprocess(self, messages, message_tree):
res = super(MailMessage, self)._message_read_dict_postprocess(
"""Preare values to be used by the chatter widget"""
res = super()._message_read_dict_postprocess(
messages, message_tree)
mail_message_ids = {m.get('id') for m in messages if m.get('id')}
mail_messages = self.browse(mail_message_ids)
partner_trackings = mail_messages.tracking_status()
failed_message = mail_messages._get_failed_message()
tracking_statuses = mail_messages.tracking_status()
for message_dict in messages:
mail_message_id = message_dict.get('id', False)
if mail_message_id:
message_dict.update({
'partner_trackings': partner_trackings[mail_message_id],
'failed_message': failed_message[mail_message_id],
})
message_dict['partner_trackings'] = \
partner_trackings[mail_message_id]
message_dict.update(tracking_statuses[mail_message_id])
return res
@api.model
def _prepare_dict_failed_message(self, message):
failed_trackings = message.mail_tracking_ids.filtered(
@api.multi
def _prepare_dict_failed_message(self):
"""Preare values to be used by the chatter widget"""
self.ensure_one()
failed_trackings = self.mail_tracking_ids.filtered(
lambda x: x.state in self.get_failed_states())
failed_partners = failed_trackings.mapped('partner_id')
failed_recipients = failed_partners.name_get()
if self.author_id:
author = self.author_id.name_get()[0]
else:
author = (-1, _('-Unknown Author-'))
return {
'id': message.id,
'date': message.date,
'author_id': message.author_id.name_get()[0],
'body': message.body,
'id': self.id,
'date': self.date,
'author': author,
'body': self.body,
'failed_recipients': failed_recipients,
}
@api.multi
def get_failed_messages(self):
return [self._prepare_dict_failed_message(msg) for msg in self]
"""Returns the list of failed messages to be used by the
failed_messages widget"""
return [msg._prepare_dict_failed_message()
for msg in self.sorted('date', reverse=True)]
@api.multi
def toggle_tracking_status(self):
"""Toggle message tracking action needed to ignore them in the tracking
issues filter"""
"""Toggle message tracking action.
This will mark them to be (or not) ignored in the tracking issues
filter.
"""
self.check_access_rule('read')
self.mail_tracking_needs_action = not self.mail_tracking_needs_action
return self.mail_tracking_needs_action
notification = {
'type': 'toggle_tracking_status',
'message_ids': [self.id],
'needs_actions': self.mail_tracking_needs_action
}
self.env['bus.bus'].sendone(
(self._cr.dbname, 'res.partner', self.env.user.partner_id.id),
notification)
def _get_failed_message_domain(self):
return [
('mail_tracking_ids.state', 'in', list(self.get_failed_states())),
('mail_tracking_needs_action', '=', True)
domain = self.env['mail.thread']._get_failed_message_domain()
domain += [
'|',
('partner_ids', 'in', [self.env.user.partner_id.id]),
('author_id', '=', self.env.user.partner_id.id),
]
return domain
@api.model
def get_failed_count(self):
""" Gets the number of failed messages """
""" Gets the number of failed messages used on discuss mailbox item"""
return self.search_count(self._get_failed_message_domain())
@api.model
def message_fetch(self, domain, limit=20):
# HACK: Because can't modify the domain in discuss JS to search the
# failed messages we force the change here to clean it of
# not valid criterias
if self.env.context.get('filter_failed_message'):
domain = self._get_failed_message_domain()
return super().message_fetch(domain, limit=limit)
def set_all_as_reviewed(self):
""" Sets all messages in the given domain as reviewed.
@api.multi
def _notify(self, force_send=False, send_after_commit=True,
user_signature=True):
self_sudo = self.sudo()
hide_followers = self_sudo._context.get('default_hide_followers',
False)
if hide_followers:
# HACK: Because Odoo uses subtype to found message followers
# whe modify it to False to avoid include them.
orig_subtype_id = self_sudo.subtype_id
self_sudo.subtype_id = False
res = super()._notify(force_send=force_send,
send_after_commit=send_after_commit,
user_signature=user_signature)
if hide_followers:
self_sudo.subtype_id = orig_subtype_id
return res
Used by Discuss """
unreviewed_messages = self.search(self._get_failed_message_domain())
unreviewed_messages.write({'mail_tracking_needs_action': False})
ids = unreviewed_messages.ids
self.env['bus.bus'].sendone(
(self._cr.dbname, 'res.partner',
self.env.user.partner_id.id),
{
'type': 'toggle_tracking_status',
'message_ids': ids,
'needs_actions': False,
}
)
return ids

View File

@ -0,0 +1,64 @@
# Copyright 2019 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, api
class MailResendMessage(models.TransientModel):
_inherit = "mail.resend.message"
@api.model
def default_get(self, fields):
rec = super().default_get(fields)
message_id = self._context.get('mail_message_to_resend')
if message_id:
MailMessageObj = self.env['mail.message']
mail_message_id = MailMessageObj.browse(message_id)
failed_states = MailMessageObj.get_failed_states()
tracking_ids = mail_message_id.mail_tracking_ids.filtered(
lambda x: x.state in failed_states)
if any(tracking_ids):
partner_ids = [(0, 0, {
"partner_id": tracking.partner_id.id,
"name": tracking.partner_id.name,
"email": tracking.partner_id.email,
"resend": True,
"message": tracking.error_description,
}) for tracking in tracking_ids]
rec['partner_ids'].extend(partner_ids)
return rec
@api.multi
def resend_mail_action(self):
for wizard in self:
to_send = wizard.partner_ids.filtered("resend").mapped(
"partner_id")
if to_send:
# Set as reviewed
wizard.mail_message_id.mail_tracking_needs_action = False
# Reset mail.tracking.email state
tracking_ids = wizard.mail_message_id.mail_tracking_ids\
.filtered(lambda x: x.partner_id in to_send)
tracking_ids.write({'state': False})
# Send bus notifications to update Discuss and
# mail_failed_messages widget
notifications = [
[
(self._cr.dbname, 'res.partner',
self.env.user.partner_id.id),
{
'type': 'update_failed_messages',
}
],
[
(self._cr.dbname, 'res.partner',
self.env.user.partner_id.id),
{
'type': 'toggle_tracking_status',
'message_ids': [self.mail_message_id.id],
'needs_actions': False,
}
]
]
self.env['bus.bus'].sendmany(notifications)
super().resend_mail_action()

View File

@ -14,12 +14,25 @@ class MailThread(models.AbstractModel):
'mail.message', 'res_id', string='Failed Messages',
domain=lambda self:
[('model', '=', self._name)]
+ self.env['mail.message']._get_failed_message_domain(),
auto_join=True)
+ self._get_failed_message_domain())
def _get_failed_message_domain(self):
"""Domain used to display failed messages on the 'failed_messages'
widget"""
failed_states = self.env['mail.message'].get_failed_states()
return [
('mail_tracking_needs_action', '=', True),
('mail_tracking_ids.state', 'in', list(failed_states)),
]
@api.multi
@api.returns('self', lambda value: value.id)
def message_post(self, *args, **kwargs):
"""Adds CC recipient to the message.
Because Odoo implementation avoid store cc recipients we ensure that
this information its written into the mail.message record.
"""
new_message = super().message_post(*args, **kwargs)
email_cc = kwargs.get('cc')
if email_cc:
@ -31,7 +44,9 @@ class MailThread(models.AbstractModel):
@api.multi
def message_get_suggested_recipients(self):
"""Adds email Cc recipients as suggested recipients.
If the recipient have an res.partner uses it."""
If the recipient has a res.partner, use it.
"""
res = super().message_get_suggested_recipients()
ResPartnerObj = self.env['res.partner']
email_cc_formated_list = []
@ -64,7 +79,7 @@ class MailThread(models.AbstractModel):
res = super().fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar,
submenu=submenu)
if view_type != 'search' and view_type != 'form':
if view_type not in {'search', 'form'}:
return res
doc = etree.XML(res['arch'])
if view_type == 'search':

View File

@ -5,6 +5,7 @@ import logging
import urllib.parse
import time
import re
import uuid
from datetime import datetime
from odoo import models, api, fields, tools
@ -91,14 +92,30 @@ class MailTrackingEmail(models.Model):
tracking_event_ids = fields.One2many(
string="Tracking events", comodel_name='mail.tracking.event',
inverse_name='tracking_email_id', readonly=True)
# Token isn't generated here to have compatibility with older trackings.
# New trackings have token and older not
token = fields.Char(string="Security Token", readonly=True,
default=lambda s: uuid.uuid4().hex,
groups="base.group_system")
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
failed_states = self.env['mail.message'].get_failed_states()
records \
.filtered(lambda one: one.state in failed_states) \
.mapped("mail_message_id") \
.write({'mail_tracking_needs_action': True})
return records
@api.multi
def write(self, vals):
if vals.get('state') in self.env['mail.message'].get_failed_states():
super().write(vals)
state = vals.get('state')
if state and state in self.env['mail.message'].get_failed_states():
self.mapped('mail_message_id').write({
'mail_tracking_needs_action': True,
})
super().write(vals)
@api.model
def email_is_bounced(self, email):
@ -162,7 +179,9 @@ class MailTrackingEmail(models.Model):
@api.depends('recipient')
def _compute_recipient_address(self):
for email in self:
if email.recipient:
is_empty_recipient = (not email.recipient
or '<False>' in email.recipient)
if not is_empty_recipient:
matches = re.search(r'<(.*@.*)>', email.recipient)
if matches:
email.recipient_address = matches.group(1).lower()
@ -187,13 +206,23 @@ class MailTrackingEmail(models.Model):
def _get_mail_tracking_img(self):
m_config = self.env['ir.config_parameter']
base_url = (m_config.get_param('mail_tracking.base.url') or
m_config.get_param('web.base.url'))
path_url = (
'mail/tracking/open/%(db)s/%(tracking_email_id)s/blank.gif' % {
'db': self.env.cr.dbname,
'tracking_email_id': self.id,
})
base_url = (m_config.get_param('mail_tracking.base.url')
or m_config.get_param('web.base.url'))
if self.token:
path_url = (
'mail/tracking/open/%(db)s/%(tracking_email_id)s/%(token)s/'
'blank.gif' % {
'db': self.env.cr.dbname,
'tracking_email_id': self.id,
'token': self.token,
})
else:
# This is here for compatibility with older records
path_url = (
'mail/tracking/open/%(db)s/%(tracking_email_id)s/blank.gif' % {
'db': self.env.cr.dbname,
'tracking_email_id': self.id,
})
track_url = urllib.parse.urljoin(base_url, path_url)
return (
'<img src="%(url)s" alt="" '
@ -216,14 +245,25 @@ class MailTrackingEmail(models.Model):
@api.multi
def smtp_error(self, mail_server, smtp_server, exception):
self.sudo().write({
'error_smtp_server': tools.ustr(smtp_server),
'error_type': exception.__class__.__name__,
'error_description': tools.ustr(exception),
values = {
'state': 'error',
})
self.sudo()._partners_email_bounced_set('error')
return True
}
IrMailServer = self.env['ir.mail_server']
if str(exception) == IrMailServer.NO_VALID_RECIPIENT \
and not self.recipient_address:
values.update({
'error_type': 'no_recipient',
'error_description':
"The partner doesn't have a defined email",
})
else:
values.update({
'error_smtp_server': tools.ustr(smtp_server),
'error_type': exception.__class__.__name__,
'error_description': tools.ustr(exception),
})
self.sudo()._partners_email_bounced_set('error')
self.sudo().write(values)
@api.multi
def tracking_img_add(self, email):

View File

@ -1,5 +1 @@
* Handle message updates on discuss 'channel_failed' instead of showing the
'outdated' message.
* Adapt chat_manager changes in v12
* Adapt discuss changes in v12
* Add pivot for tracking events and mail trackings

View File

@ -25,6 +25,9 @@ These are all available status icons:
.. |cc| image:: ../static/src/img/cc.png
:width: 10px
.. |noemail| image:: ../static/src/img/no_email.png
:width: 10px
|unknown| **Unknown**: No email tracking info available. Maybe this notified partner has 'Receive Inbox Notifications by Email' == 'Never'
|waiting| **Waiting**: Waiting to be sent
@ -39,14 +42,16 @@ These are all available status icons:
|cc| **Cc**: It's a Carbon-Copy recipient. Can't know the status so is 'Unknown'
|noemail| **No Email**: The partner doesn't have a defined email
If you want to see all tracking emails and events you can go to
* Settings > Technical > Email > Tracking emails
* Settings > Technical > Email > Tracking events
When the message generates and 'error' status, it will apear on discuss 'Failed'
channel. Any view that uses 'mail_thread' widget can show the failed messages
When the message generates an 'error' status, it will apear on discuss 'Failed'
channel. Any view with chatter can show the failed messages
too.
* Discuss
@ -56,3 +61,10 @@ too.
* Chatter
.. image:: ../static/img/failed_message_widget.png
You can use "Failed sent messages" filter present in all views to show records
with messages in failed status and that needs an user action.
* Filter
.. image:: ../static/img/failed_message_filter.png

View File

@ -376,24 +376,16 @@ right to his name.</p>
<ul class="simple">
<li><a class="reference internal" href="#installation" id="id1">Installation</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<<<<<<< HEAD
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id6">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="id7">Other credits</a><ul>
<li><a class="reference internal" href="#images" id="id8">Images</a></li>
</ul>
</li>
<li><a class="reference internal" href="#maintainers" id="id9">Maintainers</a></li>
=======
<li><a class="reference internal" href="#known-issues-roadmap" id="id3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
>>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View)
<li><a class="reference internal" href="#other-credits" id="id8">Other credits</a><ul>
<li><a class="reference internal" href="#images" id="id9">Images</a></li>
</ul>
</li>
<li><a class="reference internal" href="#maintainers" id="id10">Maintainers</a></li>
</ul>
</li>
</ul>
@ -412,47 +404,42 @@ For example, <tt class="docutils literal"><span class="pre">--load=web,mail_trac
form, then an email tracking is created for each email notification. Then a
status icon will appear just right to name of notified partner.</p>
<p>These are all available status icons:</p>
<<<<<<< HEAD
<p><img alt="unknown" src="mail_tracking/static/src/img/unknown.png" style="width: 10px;" /> <strong>Unknown</strong>: No email tracking info available. Maybe this notified partner has Receive Inbox Notifications by Email == Never</p>
<p><img alt="waiting" src="mail_tracking/static/src/img/waiting.png" style="width: 10px;" /> <strong>Waiting</strong>: Waiting to be sent</p>
<p><img alt="error" src="mail_tracking/static/src/img/error.png" style="width: 10px;" /> <strong>Error</strong>: Error while sending</p>
<p><img alt="sent" src="mail_tracking/static/src/img/sent.png" style="width: 10px;" /> <strong>Sent</strong>: Sent to SMTP server configured</p>
<p><img alt="delivered" src="mail_tracking/static/src/img/delivered.png" style="width: 15px;" /> <strong>Delivered</strong>: Delivered to final MX server</p>
<p><img alt="opened" src="mail_tracking/static/src/img/opened.png" style="width: 15px;" /> <strong>Opened</strong>: Opened by partner</p>
<p><img alt="cc" src="static/src/img/cc.png" style="width: 10px;" /> <strong>Cc</strong>: Its a Carbon-Copy recipient. Cant know the status so is Unknown</p>
<p><img alt="unknown" src="../static/src/img/unknown.png" style="width: 10px;" /> <strong>Unknown</strong>: No email tracking info available. Maybe this notified partner has Receive Inbox Notifications by Email == Never</p>
<p><img alt="waiting" src="../static/src/img/waiting.png" style="width: 10px;" /> <strong>Waiting</strong>: Waiting to be sent</p>
<p><img alt="error" src="../static/src/img/error.png" style="width: 10px;" /> <strong>Error</strong>: Error while sending</p>
<p><img alt="sent" src="../static/src/img/sent.png" style="width: 10px;" /> <strong>Sent</strong>: Sent to SMTP server configured</p>
<p><img alt="delivered" src="../static/src/img/delivered.png" style="width: 15px;" /> <strong>Delivered</strong>: Delivered to final MX server</p>
<p><img alt="opened" src="../static/src/img/opened.png" style="width: 15px;" /> <strong>Opened</strong>: Opened by partner</p>
<p><img alt="cc" src="../static/src/img/cc.png" style="width: 10px;" /> <strong>Cc</strong>: Its a Carbon-Copy recipient. Cant know the status so is Unknown</p>
<p><img alt="noemail" src="../static/src/img/no_email.png" style="width: 10px;" /> <strong>No Email</strong>: The partner doesnt have a defined email</p>
<p>If you want to see all tracking emails and events you can go to</p>
<ul class="simple">
<li>Settings &gt; Technical &gt; Email &gt; Tracking emails</li>
<li>Settings &gt; Technical &gt; Email &gt; Tracking events</li>
=======
<p><img alt="unknown" src="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/unknown.png" style="width: 10px;" /> <strong>Unknown</strong>: No email tracking info available. Maybe this notified partner has Receive Inbox Notifications by Email == Never</p>
<p><img alt="waiting" src="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/waiting.png" style="width: 10px;" /> <strong>Waiting</strong>: Waiting to be sent</p>
<p><img alt="error" src="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/error.png" style="width: 10px;" /> <strong>Error</strong>: Error while sending</p>
<p><img alt="sent" src="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/sent.png" style="width: 10px;" /> <strong>Sent</strong>: Sent to SMTP server configured</p>
<p><img alt="delivered" src="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/delivered.png" style="width: 15px;" /> <strong>Delivered</strong>: Delivered to final MX server</p>
<p><img alt="opened" src="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/opened.png" style="width: 15px;" /> <strong>Opened</strong>: Opened by partner</p>
<p><img alt="cc" src="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/cc.png" style="width: 10px;" /> <strong>Cc</strong>: Its a Carbon-Copy recipient. Cant know the status so is Unknown</p>
<p>When the message generates and error status, it will apear on discuss Failed
channel. Any view that uses mail_thread widget can show the failed messages
</ul>
<p>When the message generates an error status, it will apear on discuss Failed
channel. Any view with chatter can show the failed messages
too.</p>
<ul>
<li><p class="first">Discuss</p>
<img alt="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_discuss.png" src="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_discuss.png" />
<img alt="https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_discuss.png" src="https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_discuss.png" />
</li>
<li><p class="first">Chatter</p>
<img alt="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_widget.png" src="https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_widget.png" />
<img alt="https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_widget.png" src="https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_widget.png" />
</li>
</ul>
<p>You can use “Failed sent messages” filter present in all views to show records
with messages in failed status and that needs an user action.</p>
<ul>
<li><p class="first">Filter</p>
<img alt="https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_filter.png" src="https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_filter.png" />
</li>
</ul>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Handle message updates on discuss channel_failed instead of showing the
outdated message.</li>
<li>Adapt chat_manager changes in v12</li>
<li>Adapt discuss changes in v12</li>
<li>Add pivot for tracking events and mail trackings</li>
>>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View)
</ul>
</div>
<div class="section" id="bug-tracker">
@ -486,9 +473,9 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#id7">Other credits</a></h2>
<h2><a class="toc-backref" href="#id8">Other credits</a></h2>
<div class="section" id="images">
<h3><a class="toc-backref" href="#id8">Images</a></h3>
<h3><a class="toc-backref" href="#id9">Images</a></h3>
<ul class="simple">
<li>Odoo Community Association: <a class="reference external" href="https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg">Icon</a>.</li>
<li>Thanks to <a class="reference external" href="https://openclipart.org/user-detail/LlubNek">LlubNek</a> and <a class="reference external" href="https://openclipart.org">Openclipart</a> for <a class="reference external" href="https://openclipart.org/detail/19342/open-envelope">the icon</a>.</li>
@ -496,11 +483,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
</div>
</div>
<div class="section" id="maintainers">
<<<<<<< HEAD
<h2><a class="toc-backref" href="#id9">Maintainers</a></h2>
=======
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
>>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View)
<h2><a class="toc-backref" href="#id10">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -1,108 +0,0 @@
/* Copyright 2019 Alexandre Díaz
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
.o_mail_failed_message {
&.o_field_widget {
display: block;
}
.o_thread_date_separator
{
margin-top: 1.5rem;
margin-bottom: 3rem;
@media (max-width: @screen-xs-max) {
margin-top: 0;
margin-bottom: 1.5rem;
}
border-bottom: 1px solid @gray-lighter-darker;
border-bottom-style: solid;
text-align: center;
&.o_border_dashed {
border-bottom-style: dashed;
&[data-toggle="collapse"] {
cursor: pointer;
.o_chatter_failed_message_summary {
display: none;
}
&.collapsed {
margin-bottom: 0;
.o-transition(margin, 0.8s);
.o_chatter_failed_message_summary {
display: inline-block;
span {
padding: 0 0.5rem;
border-radius: 100%;
font-size: 1.1rem;
}
}
i.fa-caret-down:before {
content: '\f0da';
}
}
}
}
.o_thread_date {
position: relative;
top: 1rem;
margin: 0 auto;
padding: 0 1rem;
font-weight: bold;
background: white;
}
}
.o_thread_message {
display: -ms-flexbox;
display: -moz-box;
display: -webkit-box;
display: -webkit-flex;
display: flex;
padding: 0.4rem @odoo-horizontal-padding;
margin-bottom: 0px;
.o_thread_message_sidebar {
.o-flex(0, 0, @mail-thread-avatar-size);
margin-right: 1rem;
margin-top: 0.2rem;
text-align: center;
font-size: smaller;
.o_avatar_stack {
position: relative;
text-align: left;
margin-bottom: 0.8rem;
img {
.square(31px);
}
.o_avatar_icon {
.o-position-absolute(@right: -5px, @bottom: -5px);
.square(25px);
padding: 0.6rem 0.5rem;
text-align: center;
line-height: 1.2;
color: white;
border-radius: 100%;
border: 2px solid white;
}
}
}
.o_thread_message_core .o_mail_info {
.text-muted();
}
}
}
.o_mail_chat .o_mail_chat_sidebar .o_mail_failed_message_refresh {
margin-right: 0.5em;
margin-top: 0.2em;
}

View File

@ -0,0 +1,343 @@
/* Copyright 2019 Alexandre Díaz
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
// FIXME: More of these classes are cloned from other scss files.
.o_mail_failed_message {
&.o_field_widget {
display: block;
}
.o_thread_date_separator.o_border_dashed {
border-bottom-style: dashed;
&[data-toggle="collapse"] {
cursor: pointer;
.o_chatter_failed_message_summary {
display: none;
}
&.collapsed {
margin-bottom: 0;
transition: margin 0.8s ease 0s;
.o_chatter_failed_message_summary {
display: inline-block;
span {
padding: 0 5px;
border-radius: 100%;
font-size: 11px;
}
}
i.fa-caret-down:before {
content: '\f0da';
}
}
}
}
.o_thread_show_more {
text-align: center;
}
.o_mail_thread_content {
display: flex;
flex-direction: column;
min-height: 100%;
}
.o_thread_bottom_free_space {
height: 15px;
}
.o_thread_typing_notification_free_space {
flex-grow: 1,
}
.o_thread_typing_notification_bar {
flex: 0, 0, 20px;
background-color: rgba($white, 0.75);
padding: 5px;
text-align: center;
color: gray('600');
&.o_thread_order_asc {
@include o-position-sticky($bottom: 0px);
}
&.o_thread_order_desc {
@include o-position-sticky($top: 0px);
}
}
.o_thread_tooltip_container {
display: inline;
position: relative;
}
.o_thread_date_separator {
margin-top: 15px;
margin-bottom: 30px;
@include media-breakpoint-down(sm) {
margin-top: 0px;
margin-bottom: 15px;
}
border-bottom: 1px solid gray('400');
text-align: center;
.o_thread_date {
position: relative;
top: 10px;
margin: 0 auto;
padding: 0 10px;
font-weight: bold;
background: white;
}
}
.o_thread_new_messages_separator {
margin-bottom: 15px;
border-bottom: solid lighten($o-brand-odoo, 15%) 1px;
text-align: right;
.o_thread_separator_label {
position: relative;
top: 8px;
padding: 0 10px;
background: white;
color: lighten($o-brand-odoo, 15%);
font-size: smaller;
}
}
.o_thread_message {
display: flex;
padding: 4px $o-horizontal-padding;
margin-bottom: 0px;
&.o_mail_not_discussion {
background-color: rgba(gray('300'), 0.5);
border-bottom: 1px solid gray('400');
}
.o_thread_message_sidebar {
flex: 0 0 $o-mail-thread-avatar-size;
margin-right: 10px;
margin-top: 2px;
text-align: center;
font-size: smaller;
@include media-breakpoint-down(sm) {
margin-top: 4px;
font-size: x-small;
}
.o_thread_message_avatar {
max-width: $o-mail-thread-avatar-size;
}
.o_thread_message_side_date {
margin-left: -5px;
}
.o_thread_message_star {
margin-right: -5px;
}
.o_thread_message_side_date {
opacity: 0;
}
}
.o_thread_icon {
cursor: pointer;
opacity: 0;
&.fa-star {
opacity: $o-mail-thread-icon-opacity;
color: gold;
}
}
&:hover, &.o_thread_selected_message {
.o_thread_message_side_date {
opacity: $o-mail-thread-side-date-opacity;
}
.o_thread_icon {
opacity: $o-mail-thread-icon-opacity;
&:hover {
opacity: 1;
}
}
}
.o_mail_redirect {
cursor: pointer;
}
.o_thread_message_core {
flex: 1 1 auto;
min-width: 0;
max-width: 100%;
word-wrap: break-word;
> pre {
white-space: pre-wrap;
word-break: break-word;
text-align: justify;
}
.o_mail_subject {
font-style: italic;
}
.o_mail_notification {
font-style: italic;
color: gray;
}
[summary~=o_mail_notification] { // name conflicts with channel notifications, but is odoo notification buttons to hide in chatter if present
display: none;
}
p {
margin: 0 0 9px; // Required by the old design to override a general rule on p's
&:last-child {
margin-bottom: 0;
}
}
a {
display: inline-block;
word-break: break-all;
}
:not(.o_image_box) > img {
max-width: 100%;
height: auto;
}
.o_mail_body_long {
display: none;
}
.o_mail_info {
margin-bottom: 2px;
strong {
color: $headings-color;
}
}
.o_thread_message_star, .o_thread_message_needaction, .o_thread_message_reply, .o_thread_message_email {
padding: 4px;
}
i.o_thread_message_email {
&.o_thread_message_email_ready {
color: grey;
}
&.o_thread_message_email_exception, &.o_thread_message_email_bounce {
color: red;
opacity: 1;
cursor: pointer;
}
}
.o_attachments_list, .o_attachments_previews {
&:last-child {
margin-bottom: $grid-gutter-width;
}
}
.o_thread_tooltip_container {
display: inline;
position: relative;
}
}
}
.o_thread_title {
margin-top: 20px;
margin-bottom: 20px;
font-weight: bold;
font-size: 125%;
}
.o_mail_no_content {
@include o-position-absolute(30%, 0, 0, 0);
text-align: center;
font-size: 115%;
}
.o_thread_message .o_thread_message_core .o_mail_read_more {
display: block;
}
#o_chatter_failed_message {
.o_thread_message {
.o_thread_message_sidebar {
.o_avatar_stack {
position: relative;
text-align: left;
margin-bottom: 8px;
img {
width: 31px;
height: 31px;
}
.o_avatar_icon {
@include o-position-absolute($right: -5px, $bottom: -5px);
width: 25px;
height: 25px;
padding: 6px 5px;
text-align: center;
line-height: 1.2;
color: white;
border-radius: 100%;
border: 2px solid white;
}
}
}
.o_mail_info {
.o_activity_info {
vertical-align: baseline;
padding: 4px 6px;
background: theme-color('light');
border-radius: 2px 2px 0 0;
@include o-hover-opacity(1, 1);
&.collapsed {
@include o-hover-opacity(0.5, 1);
background: transparent;
}
}
}
.o_thread_message_collapse .dl-horizontal.card {
display: inline-block;
margin-bottom: 0;
dt {
max-width: 80px;
}
dd {
margin-left: 95px;
}
}
.o_thread_message_note {
margin: 2px 0 5px;
padding: 0px;
}
.o_thread_message_warning {
margin: 2px 0 5px;
}
.o_thread_message_tools {
.o_failed_message_link {
padding: 0 $input-btn-padding-x;
}
.o_failed_message_retry {
padding-left: 0;
}
}
}
}
}

View File

@ -4,10 +4,10 @@
.mail_tracking {
span {
color: @odoo-color-0;
color: #909090;
&.mail_tracking_opened {
color: @odoo-color-5;
color: #a34a8b;
}
}
}

View File

@ -1,365 +0,0 @@
/* Copyright 2019 Alexandre Díaz
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
odoo.define('mail_tracking.FailedMessage', function (require) {
"use strict";
var ChatAction = require('mail.chat_client_action');
var AbstractField = require('web.AbstractField');
var BasicModel = require('web.BasicModel');
var BasicView = require('web.BasicView');
var Chatter = require('mail.Chatter');
var utils = require('mail.utils');
var chat_manager = require('mail.chat_manager');
var core = require('web.core');
var field_registry = require('web.field_registry');
var time = require('web.time');
var session = require('web.session');
var config = require('web.config');
var QWeb = core.qweb;
var _t = core._t;
/* DISCUSS */
var failed_counter = 0;
var is_channel_failed_outdated = false;
ChatAction.include({
init: function () {
this._super.apply(this, arguments);
// HACK: Custom event to update messsages
core.bus.on('force_update_message', this, function (data) {
is_channel_failed_outdated = true;
this._onMessageUpdated(data);
this.throttledUpdateChannels();
});
},
_renderSidebar: function (options) {
options.failed_counter = chat_manager.get_failed_counter();
return this._super.apply(this, arguments);
},
_onMessageUpdated: function (message, type) {
var self = this;
var current_channel_id = this.channel.id;
// HACK: break inheritance because can't override properly
if (current_channel_id === "channel_failed" &&
!message.is_failed) {
chat_manager.get_messages({
channel_id: this.channel.id,
domain: this.domain,
}).then(function (messages) {
var options = self._getThreadRenderingOptions(messages);
self.thread.remove_message_and_render(
message.id, messages, options).then(function () {
self._updateButtonStatus(messages.length === 0, type);
});
});
} else {
this._super.apply(this, arguments);
}
},
_updateChannels: function () {
var self = this;
// HACK: break inheritance because can't override properly
if (this.channel.id === "channel_failed") {
var $sidebar = this._renderSidebar({
active_channel_id:
this.channel ? this.channel.id: undefined,
channels: chat_manager.get_channels(),
needaction_counter: chat_manager.get_needaction_counter(),
starred_counter: chat_manager.get_starred_counter(),
failed_counter: chat_manager.get_failed_counter(),
});
this.$(".o_mail_chat_sidebar").html($sidebar.contents());
_.each(['dm', 'public', 'private'], function (type) {
var $input = self.$(
'.o_mail_add_channel[data-type=' + type + '] input');
self._prepareAddChannelInput($input, type);
});
} else {
this._super.apply(this, arguments);
}
// FIXME: Because can't refresh "channel_failed" we add a flag
// to indicate that the data is outdated
var refresh_elm = this.$(
".o_mail_chat_sidebar .o_mail_failed_message_refresh");
refresh_elm.click(function (event) {
event.preventDefault();
event.stopPropagation();
location.reload();
});
if (is_channel_failed_outdated) {
refresh_elm.removeClass('hidden');
}
},
});
chat_manager.get_failed_counter = function () {
return failed_counter;
};
chat_manager._onMailClientAction_failed_message_super =
chat_manager._onMailClientAction;
chat_manager._onMailClientAction = function (result) {
failed_counter = result.failed_counter;
return this._onMailClientAction_failed_message_super(result);
};
function add_channel_to_message (message, channel_id) {
message.channel_ids.push(channel_id);
message.channel_ids = _.uniq(message.channel_ids);
}
chat_manager._make_message_failed_message_super = chat_manager.make_message;
chat_manager.make_message = function (data) {
var msg = this._make_message_failed_message_super(data);
function property_descr (channel) {
return {
enumerable: true,
get: function () {
return _.contains(msg.channel_ids, channel);
},
set: function (bool) {
if (bool) {
add_channel_to_message(msg, channel);
} else {
msg.channel_ids = _.without(msg.channel_ids, channel);
}
},
};
}
Object.defineProperties(msg, {
is_failed: property_descr("channel_failed"),
});
msg.is_failed = data.failed_message;
return msg;
};
chat_manager._fetchFromChannel_failed_message_super =
chat_manager._fetchFromChannel;
chat_manager._fetchFromChannel = function (channel, options) {
if (channel.id !== "channel_failed") {
return this._fetchFromChannel_failed_message_super(
channel, options);
}
// HACK: Can't override '_fetchFromChannel' properly to modify the
// domain, uses context instead and does it in python.
session.user_context.filter_failed_message = true;
var res = this._fetchFromChannel_failed_message_super(
channel, options);
res.then(function () {
delete session.user_context.filter_failed_message;
});
return res;
};
// HACK: Get failed_counter. Because 'chat_manager' call 'start' need call
// to '/mail/client_action' again with overrided '_onMailClientAction'
session.is_bound.then(function () {
var context = _.extend({isMobile: config.device.isMobile},
session.user_context);
return session.rpc('/mail/client_action', {context: context});
}).then(chat_manager._onMailClientAction.bind(chat_manager));
/* FAILED MESSAGES CHATTER WIDGET */
// TODO: Use timeFromNow() in v12
function time_from_now (date) {
if (moment().diff(date, 'seconds') < 45) {
return _t("now");
}
return date.fromNow();
}
function _readMessages (self, ids) {
if (!ids.length) {
return $.when([]);
}
var context = self.record && self.record.getContext();
return self._rpc({
model: 'mail.message',
method: 'get_failed_messages',
args: [ids],
context: context || self.getSession().user_context,
}).then(function (messages) {
// Convert date to moment
_.each(messages, function (msg) {
msg.date = moment(time.auto_str_to_date(msg.date));
msg.hour = time_from_now(msg.date);
});
return _.sortBy(messages, 'date');
});
}
BasicModel.include({
_fetchSpecialFailedMessages: function (record, fieldName) {
var localID = record._changes && fieldName in record._changes
? record._changes[fieldName] : record.data[fieldName];
return _readMessages(this, this.localData[localID].res_ids);
},
});
var AbstractFailedMessagesField = AbstractField.extend({
_markFailedMessageReviewed: function (id) {
return this._rpc({
model: 'mail.message',
method: 'toggle_tracking_status',
args: [[id]],
context: this.record.getContext(),
}).then(function (status) {
var fake_message = {
'id': id,
'is_failed': status,
};
chat_manager.bus.trigger('update_message', fake_message);
core.bus.trigger('force_update_message', fake_message);
});
},
});
var FailedMessage = AbstractFailedMessagesField.extend({
className: 'o_mail_failed_message',
events: {
'click .o_failed_message_retry': '_onRetryFailedMessage',
'click .o_failed_message_reviewed': '_onMarkFailedMessageReviewed',
},
specialData: '_fetchSpecialFailedMessages',
init: function () {
this._super.apply(this, arguments);
this.failed_messages = this.record.specialData[this.name];
},
_render: function () {
if (this.failed_messages.length) {
this.$el.html(QWeb.render(
'mail_tracking.failed_message_items', {
failed_messages: this.failed_messages,
nbFailedMessages: this.failed_messages.length,
date_format: time.getLangDateFormat(),
datetime_format: time.getLangDatetimeFormat(),
}));
} else {
this.$el.empty();
}
},
_reset: function (record) {
this._super.apply(this, arguments);
this.failed_messages = this.record.specialData[this.name];
this.res_id = record.res_id;
},
_reload: function (fieldsToReload) {
this.trigger_up('reload_mail_fields', fieldsToReload);
},
_openComposer: function (context) {
var self = this;
this.do_action({
type: 'ir.actions.act_window',
res_model: 'mail.compose.message',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'new',
context: context,
}, {
on_close: function () {
self._reload({failed_message: true});
self.trigger('need_refresh');
chat_manager.get_messages({
model: self.model,
res_id: self.res_id,
});
},
}).then(this.trigger.bind(this, 'close_composer'));
},
// Handlers
_onRetryFailedMessage: function (event) {
event.preventDefault();
var message_id = $(event.currentTarget).data('message-id');
var failed_msg = _.findWhere(this.failed_messages,
{'id': message_id});
var failed_partner_ids = _.map(failed_msg.failed_recipients,
function (item) {
return item[0];
});
this._openComposer({
default_body: utils.get_text2html(failed_msg.body),
default_partner_ids: failed_partner_ids,
default_is_log: false,
default_model: this.model,
default_res_id: this.res_id,
default_composition_mode: 'comment',
// Omit followers
default_hide_followers: true,
mail_post_autofollow: true,
message_id: message_id,
});
},
_onMarkFailedMessageReviewed: function (event) {
event.preventDefault();
var message_id = $(event.currentTarget).data('message-id');
this._markFailedMessageReviewed(message_id).then(
this._reload.bind(this, {failed_message: true}));
},
});
field_registry.add('mail_failed_message', FailedMessage);
var mailWidgets = ['mail_failed_message'];
BasicView.include({
init: function (viewInfo) {
this._super.apply(this, arguments);
// Adds mail_failed_message as valid mail widget
var fieldsInfo = viewInfo.fieldsInfo[this.viewType];
for (var fieldName in fieldsInfo) {
var fieldInfo = fieldsInfo[fieldName];
if (_.contains(mailWidgets, fieldInfo.widget)) {
this.mailFields[fieldInfo.widget] = fieldName;
fieldInfo.__no_fetch = true;
}
}
Object.assign(this.rendererParams.mailFields, this.mailFields);
},
});
Chatter.include({
init: function (parent, record, mailFields, options) {
this._super.apply(this, arguments);
// Initialize mail_failed_message widget
if (mailFields.mail_failed_message) {
this.fields.failed_message = new FailedMessage(
this, mailFields.mail_failed_message, record, options);
}
},
_render: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
if (self.fields.failed_message) {
self.fields.failed_message.$el.insertBefore(
self.$el.find('.o_mail_thread'));
}
});
},
_onReloadMailFields: function (event) {
this._super.apply(this, arguments);
var fieldNames = [];
if (this.fields.failed_message && event.data.failed_message) {
fieldNames.push(this.fields.failed_message.name);
}
this.trigger_up('reload', {
fieldNames: fieldNames,
keepChanges: true,
});
},
});
return FailedMessage;
});

View File

@ -0,0 +1,397 @@
/* Copyright 2019 Alexandre Díaz
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
odoo.define('mail_tracking.FailedMessageDiscuss', function (require) {
"use strict";
// To be considered:
// - One message can be displayed in many threads
// - A thread can be a mailbox, channel, ...
// - A mailbox is a type of thread that is displayed on top of
// the discuss menu, has a counter, etc...
var MailManagerNotif = require('mail.Manager.Notification');
var AbstractMessage = require('mail.model.AbstractMessage');
var Message = require('mail.model.Message');
var Discuss = require('mail.Discuss');
var MailManager = require('mail.Manager');
var Mailbox = require('mail.model.Mailbox');
var core = require('web.core');
var session = require('web.session');
var QWeb = core.qweb;
var _t = core._t;
/* The states to consider a message as failed message */
var FAILED_STATES = [
'error', 'rejected', 'spam', 'bounced', 'soft-bounced',
];
AbstractMessage.include({
/**
* Abstract declaration to know if a message is included in the
* failed mailbox. By default it should be false.
*
* @returns {Boolean}
*/
isFailed: function () {
return false;
},
});
Message.include({
/**
* Overrides to store information from server
*
* @override
*/
init: function (parent, data) {
this._isFailedMessage = data.is_failed_message;
return this._super.apply(this, arguments);
},
/**
* Implementation to know if a message is included in the
* failed mailbox.
*
* @override
*/
isFailed: function () {
return _.contains(this._threadIDs, 'mailbox_failed');
},
/**
* Adds/Removes message to/from failed mailbox
*
* @param {Boolean} failed
*/
setFailed: function (failed) {
if (failed) {
this._addThread('mailbox_failed');
} else {
this.removeThread('mailbox_failed');
}
},
/**
* Include the message in the 'failed' mailbox if needed
*
* @override
*/
_processMailboxes: function () {
this.setFailed(this._isFailedMessage);
return this._super.apply(this, arguments);
},
});
MailManagerNotif.include({
/**
* Overrides to handle changes in the 'mail_tracking_needs_action' flag
*
* @override
*/
_handlePartnerNotification: function (data) {
if (data.type === 'toggle_tracking_status') {
this._handlePartnerToggleFailedNotification(data);
} else {
// Workaround to avoid call '_handlePartnerChannelNotification'
// because this is related with the failed mailbox, not a
// channel.
this._super.apply(this, arguments);
}
},
/**
* This method updates messages in the failed mailbox when the flag
* 'mail_tracking_needs_action' is toggled. This can remove/add
* the message from/to failed mailbox and update mailbox counter.
*
* @private
* @param {Object} data
*/
_handlePartnerToggleFailedNotification: function (data) {
var self = this;
var failed = this.getMailbox('failed');
_.each(data.message_ids, function (messageID) {
var message = _.find(self._messages, function (msg) {
return msg.getID() === messageID;
});
if (message) {
message.setFailed(data.needs_actions);
if (message.isFailed() === false) {
self._removeMessageFromThread(
'mailbox_failed', message);
} else {
self._addMessageToThreads(message, []);
var channelFailed = self.getMailbox('failed');
channelFailed.invalidateCaches();
}
self._mailBus.trigger('update_message', message, data.type);
}
});
if (data.needs_actions) {
// Increase failed counter if message is marked as failed
failed.incrementMailboxCounter(data.message_ids.length);
} else {
// Decrease failed counter if message is removed from failed
failed.decrementMailboxCounter(data.message_ids.length);
}
// Trigger event to refresh threads
this._mailBus.trigger('update_failed', failed.getMailboxCounter());
},
});
Discuss.include({
events: _.extend({}, Discuss.prototype.events, {
'click .o_failed_message_retry': '_onRetryFailedMessage',
'click .o_failed_message_reviewed': '_onMarkFailedMessageReviewed',
}),
/**
* Paramaters used to render 'failed' mailbox entry in Discuss
*
* @private
* @returns {Object}
*/
_sidebarQWebParams: function () {
var failed = this.call('mail_service', 'getMailbox', 'failed');
return {
activeThreadID: this._thread ? this._thread.getID() : undefined,
failedCounter: failed.getMailboxCounter(),
};
},
/**
* Render 'failed' mailbox menu entry in Discuss
*
* @private
* @returns {jQueryElementt}
*/
_renderSidebar: function () {
var $sidebar = this._super.apply(this, arguments);
// Because Odoo implementation isn't designed to be inherited
// properly, we inject 'failed' button using jQuery.
var $failed_item = $(QWeb.render('mail_tracking.SidebarFailed',
this._sidebarQWebParams()));
$failed_item.insertAfter(
$sidebar.find(".o_mail_discuss_title_main").filter(":last"));
return $sidebar;
},
/**
* Overrides to listen click on 'Set all as reviewed' button
*
* @override
*/
_renderButtons: function () {
this._super.apply(this, arguments);
this.$btn_set_all_reviewed = this.$buttons.find(
'.o_mail_discuss_button_set_all_reviewed');
this.$btn_set_all_reviewed
.on('click', $.proxy(this, "_onSetAllAsReviewedClicked"));
},
/**
* Show or hide 'set all as reviewed' button in discuss mailbox
*
* This means in which thread the button should be displayed.
*
* @override
*/
_updateControlPanelButtons: function (thread) {
this.$btn_set_all_reviewed
.toggleClass(
'd-none d-md-none',
thread.getID() !== 'mailbox_failed');
return this._super.apply(this, arguments);
},
/**
* Overrides to update 'set all as reviewed' button.
*
* Disabled button if doesn't have more failed messages
*
* @override
*/
_updateButtonStatus: function (disabled, type) {
if (this._thread.getID() === 'mailbox_failed') {
this.$btn_set_all_reviewed
.toggleClass('disabled', disabled);
// Display Rainbowman when all failed messages are reviewed
// through 'TOGGLE TRACKING STATUS' or marking last failed
// message as reviewed
if (disabled && type === 'toggle_tracking_status') {
this.trigger_up('show_effect', {
message: _t(
"Congratulations, your failed mailbox is empty"),
type: 'rainbow_man',
});
}
}
},
/**
* Overrides to update messages in 'failed' mailbox thread
*
* @override
*/
_onMessageUpdated: function (message, type) {
var self = this;
var currentThreadID = this._thread.getID();
if (currentThreadID === 'mailbox_failed' && !message.isFailed()) {
this._thread.fetchMessages(this.domain)
.then(function () {
var options = self._getThreadRenderingOptions();
self._threadWidget.removeMessageAndRender(
message.getID(), self._thread, options)
.then(function () {
self._updateButtonStatus(
!self._thread.hasMessages(), type);
});
});
} else {
// Workaround to avoid calling '_fetchAndRenderThread' and
// refetching thread messages because these messages are
// actually fetched above.
this._super.apply(this, arguments);
}
},
/**
* Hide reply feature in the 'failed' mailbox, where it has no sense.
* Show instead 'Retry' and 'Set as reviewed' buttons.
*
* @override
*/
_getThreadRenderingOptions: function () {
var values = this._super.apply(this, arguments);
if (this._thread.getID() === 'mailbox_failed') {
values.displayEmailIcons = true;
values.displayReplyIcons = false;
values.displayRetryButton = true;
values.displayReviewedButton = true;
}
return values;
},
/**
* Listen also to the event that refreshes thread messages
*
* @override
*/
_startListening: function () {
this._super.apply(this, arguments);
this.call('mail_service', 'getMailBus')
.on('update_failed', this, this._throttledUpdateThreads);
},
// Handlers
/**
* Open the resend mail.resend.message wizard
*
* @private
* @param {Event} event
*/
_onRetryFailedMessage: function (event) {
event.preventDefault();
var messageID = $(event.currentTarget).data('message-id');
this.do_action('mail.mail_resend_message_action', {
additional_context: {
mail_message_to_resend: messageID,
},
});
},
/**
* Toggle 'mail_tracking_needs_action' flag
*
* @private
* @param {Event} event
* @returns {Promise}
*/
_onMarkFailedMessageReviewed: function (event) {
event.preventDefault();
var messageID = $(event.currentTarget).data('message-id');
return this._rpc({
model: 'mail.message',
method: 'toggle_tracking_status',
args: [[messageID]],
context: this.getSession().user_context,
});
},
/**
* Inheritable method that call thread implementation
*
* @private
*/
_onSetAllAsReviewedClicked: function () {
this._thread.setAllMessagesAsReviewed();
},
});
MailManager.include({
/**
* Add the 'failed' mailbox
*
* @override
*/
_updateMailboxesFromServer: function (data) {
this._super.apply(this, arguments);
this._addMailbox({
id: 'failed',
name: _t("Failed"),
mailboxCounter: data.failed_counter || 0,
});
},
});
Mailbox.include({
/**
* Overrides to add domain for 'failed' mailbox thread
*
* @override
*/
_getThreadDomain: function () {
if (this._id === 'mailbox_failed') {
return [
['mail_tracking_ids.state', 'in', FAILED_STATES],
['mail_tracking_needs_action', '=', true],
'|',
['partner_ids', 'in', [session.partner_id]],
['author_id', '=', session.partner_id],
];
}
// Workaround to avoid throw 'Missing domain' exception. Call _super
// without a valid (hard-coded) thread id causes that exeception.
return this._super.apply(this, arguments);
},
/**
* Sets all messages from the mailbox as reviewed.
*
* At the moment, this method makes only sense for 'Failed'.
*
* @returns {$.Promise} resolved when all messages have been marked as
* reviewed on the server
*/
setAllMessagesAsReviewed: function () {
if (this._id === 'mailbox_failed' && this.getMailboxCounter() > 0) {
return this._rpc({
model: 'mail.message',
method: 'set_all_as_reviewed',
});
}
return $.when();
},
});
});

View File

@ -0,0 +1,316 @@
/* Copyright 2019 Alexandre Díaz
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
odoo.define('mail_tracking.FailedMessageThread', function (require) {
"use strict";
var AbstractField = require('web.AbstractField');
var BasicModel = require('web.BasicModel');
var BasicView = require('web.BasicView');
var Chatter = require('mail.Chatter');
var MailThread = require('mail.widget.Thread');
var utils = require('mail.utils');
var core = require('web.core');
var field_registry = require('web.field_registry');
var time = require('web.time');
var QWeb = core.qweb;
/**
* Helper method to fetch failed messages
*
* @private
* @param {Object} widget
* @param {Array} ids
* @returns {Array}
*/
function _readMessages (widget, ids) {
if (!ids.length) {
return $.when();
}
var context = widget.record && widget.record.getContext();
return widget._rpc({
model: 'mail.message',
method: 'get_failed_messages',
args: [ids],
context: context || widget.getSession().user_context,
}).then(function (messages) {
// Convert date to moment
_.each(messages, function (msg) {
msg.date = moment(time.auto_str_to_date(msg.date));
msg.hour = utils.timeFromNow(msg.date);
});
return messages;
});
}
BasicModel.include({
/**
* Fetch data for the 'mail_failed_message' field widget in form views.
*
* @private
* @param {Object} record
* @param {String} fieldName
* @returns {Array}
*/
_fetchSpecialFailedMessages: function (record, fieldName) {
var localID = record._changes && fieldName in record._changes
? record._changes[fieldName] : record.data[fieldName];
return _readMessages(this, this.localData[localID].res_ids);
},
});
var FailedMessage = AbstractField.extend({
className: 'o_mail_failed_message',
events: {
'click .o_failed_message_retry': '_onRetryFailedMessage',
'click .o_failed_message_reviewed': '_onMarkFailedMessageReviewed',
},
specialData: '_fetchSpecialFailedMessages',
/**
* Overrides to reference failed messages in a easy way
*
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.failed_messages = this.record.specialData[this.name] || [];
},
/**
* Overrides to listen bus notifications
*
* @override
*/
start: function () {
this._super.apply(this, arguments);
this.call(
'bus_service', 'onNotification', this, this._onNotification);
},
/**
* Paremeters used to render widget
*
* @private
* @returns {Object}
*/
_failedItemsQWebParams: function () {
return {
failed_messages: this.failed_messages,
nbFailedMessages: this.failed_messages.length,
date_format: time.getLangDateFormat(),
datetime_format: time.getLangDatetimeFormat(),
};
},
/**
* @private
*/
_render: function () {
if (this.failed_messages.length) {
this.$el.html(QWeb.render(
'mail_tracking.failed_message_items',
this._failedItemsQWebParams()));
} else {
this.$el.empty();
}
},
/**
* Reset widget data using selected record
*
* @private
* @param {Object} record
*/
_reset: function (record) {
this._super.apply(this, arguments);
this.failed_messages = this.record.specialData[this.name] || [];
this.res_id = record.res_id;
},
/**
* Trigger event to reload mail widgets
*
* @private
* @param {Array} fieldsToReload
*/
_reload: function (fieldsToReload) {
this.trigger_up('reload_mail_fields', fieldsToReload);
},
/**
* Mark failed message as reviewed
*
* @private
* @param {Int} id
* @returns {Promise}
*/
_markFailedMessageReviewed: function (id) {
return this._rpc({
model: 'mail.message',
method: 'toggle_tracking_status',
args: [[id]],
context: this.record.getContext(),
});
},
// Handlers
/**
* Listen bus notification to launch reload process.
* This bus notification is received when the user uses
* 'mail.resend.message' wizard.
*
* @private
* @param {Array} notifs
*/
_onNotification: function (notifs) {
var self = this;
_.each(notifs, function (notif) {
var model = notif[0][1];
if (model === 'res.partner') {
var data = notif[1];
if (data.type === 'update_failed_messages') {
// Reload 'mail_failed_message' widget
self._reload({failed_message: true});
}
}
});
},
/**
* Handle retry failed message event to open the mail.resend.message
* wizard.
*
* @private
* @param {Event} event
*/
_onRetryFailedMessage: function (event) {
event.preventDefault();
var messageID = $(event.currentTarget).data('message-id');
this.do_action('mail.mail_resend_message_action', {
additional_context: {
mail_message_to_resend: messageID,
},
});
},
/**
* Handle mark message as reviewed event
*
* @private
* @param {Event} event
*/
_onMarkFailedMessageReviewed: function (event) {
event.preventDefault();
var messageID = $(event.currentTarget).data('message-id');
this._markFailedMessageReviewed(messageID).then(
$.proxy(this, "_reload", {failed_message: true}));
},
});
field_registry.add('mail_failed_message', FailedMessage);
var mailWidgets = ['mail_failed_message'];
BasicView.include({
/**
* Overrides to add 'mail_failed_message' widget as "mail widget" used
* in Chatter.
*
* @override
*/
init: function () {
this._super.apply(this, arguments);
var fieldsInfo = this.fieldsInfo[this.viewType];
for (var fieldName in fieldsInfo) {
var fieldInfo = fieldsInfo[fieldName];
// Search fields using 'mail_failed_messsage' widget.
// Only one field can exists using the widget, the last
// found wins.
if (_.contains(mailWidgets, fieldInfo.widget)) {
// Add field as "mail field" shared with Chatter
this.mailFields[fieldInfo.widget] = fieldName;
// Avoid fetch x2many data, this will be done by widget
fieldInfo.__no_fetch = true;
}
}
// Update renderParmans mailFields to include the found field
// using 'mail_failed_messsage' widget. This info is used by the
// renderers [In Odoo vanilla by the form renderer to initialize
// Chatter widget].
_.extend(this.rendererParams.mailFields, this.mailFields);
},
});
Chatter.include({
/**
* Overrides to initialize 'mail_failed_message' widget.
*
* @override
*/
init: function (parent, record, mailFields, options) {
this._super.apply(this, arguments);
// Initialize mail_failed_message widget
if (mailFields.mail_failed_message) {
this.fields.failed_message = new FailedMessage(
this, mailFields.mail_failed_message, record, options);
}
},
/**
* Injects failed messages widget before the chatter
*
* @private
* @returns {Promise}
*/
_render: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
if (self.fields.failed_message) {
self.fields.failed_message.$el.insertBefore(
self.$el.find('.o_mail_thread'));
}
});
},
/**
* Overrides to reload 'mail_failed_message' widget
*
* @override
*/
_onReloadMailFields: function (event) {
if (this.fields.failed_message && event.data.failed_message) {
this.trigger_up('reload', {
fieldNames: [this.fields.failed_message.name],
keepChanges: true,
});
} else {
// Workaround to avoid trigger reload event twice (once for
// mail_failed_message and again with empty 'fieldNames'.
this._super.apply(this, arguments);
}
},
});
MailThread.include({
/**
* Show 'retry' & 'Set as reviewed' buttons in the Chatter
*
* @override
*/
init: function () {
this._super.apply(this, arguments);
this._enabledOptions.displayRetryButton = true;
this._enabledOptions.displayReviewedButton = true;
this._disabledOptions.displayRetryButton = false;
this._disabledOptions.displayReviewedButton = false;
},
});
return FailedMessage;
});

View File

@ -2,7 +2,7 @@
Copyright 2018 David Vidal - <david.vidal@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
odoo.define('mail_tracking.partner_tracking', function(require){
odoo.define('mail_tracking.partner_tracking', function (require) {
"use strict";
var core = require('web.core');
@ -14,10 +14,11 @@ odoo.define('mail_tracking.partner_tracking', function(require){
var _t = core._t;
AbstractMessage.include({
/**
* Messages do not have any PartnerTrackings.
*
* @return {boolean}
* @returns {Boolean}
*/
hasPartnerTrackings: function () {
return false;
@ -26,7 +27,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){
/**
* Messages do not have any email Cc values.
*
* @return {boolean}
* @returns {Boolean}
*/
hasEmailCc: function () {
return false;
@ -34,7 +35,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){
});
Message.include({
init: function (parent, data, emojis) {
init: function (parent, data) {
this._super.apply(this, arguments);
this._partnerTrackings = data.partner_trackings || [];
this._emailCc = data.email_cc || [];
@ -45,7 +46,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){
* State whether this message contains some PartnerTrackings values
*
* @override
* @return {boolean}
* @returns {Boolean}
*/
hasPartnerTrackings: function () {
return _.some(this._partnerTrackings);
@ -54,7 +55,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){
/**
* State whether this message contains some email Cc values
*
* @return {boolean}
* @returns {Boolean}
*/
hasEmailCc: function () {
return _.some(this._emailCc);
@ -65,7 +66,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){
* If this message has no PartnerTrackings values, returns []
*
* @override
* @return {Object[]}
* @returns {Object[]}
*/
getPartnerTrackings: function () {
if (!this.hasPartnerTrackings()) {
@ -78,7 +79,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){
* Get the email Cc values of this message
* If this message has no email Cc values, returns []
*
* @return {Array}
* @returns {Array}
*/
getEmailCc: function () {
if (!this.hasEmailCc()) {
@ -91,7 +92,8 @@ odoo.define('mail_tracking.partner_tracking', function(require){
* Check if the email is an Cc
* If this message has no email Cc values, returns false
*
* @return {Boolean}
* @param {String} email
* @returns {Boolean}
*/
isEmailCc: function (email) {
if (!this.hasEmailCc()) {
@ -104,31 +106,19 @@ odoo.define('mail_tracking.partner_tracking', function(require){
toggleTrackingStatus: function () {
return this._rpc({
model: 'mail.message',
method: 'toggle_tracking_status',
args: [[this.id]],
});
model: 'mail.message',
method: 'toggle_tracking_status',
args: [[this.id]],
});
},
});
ThreadWidget.include({
events: _.extend(ThreadWidget.prototype.events, {
'click .o_mail_action_tracking_partner': 'on_tracking_partner_click',
'click .o_mail_action_tracking_partner':
'on_tracking_partner_click',
'click .o_mail_action_tracking_status': 'on_tracking_status_click',
}),
_preprocess_message: function () {
var msg = this._super.apply(this, arguments);
msg.partner_trackings = msg.partner_trackings || [];
var needs_action = msg.track_needs_action;
var message_track = _.findWhere(messages_tracked_changes, {
id: msg.id,
});
if (message_track) {
needs_action = message_track.status;
}
msg.track_needs_action = needs_action;
return msg;
},
on_tracking_partner_click: function (event) {
var partner_id = this.$el.find(event.currentTarget).data('partner');
var state = {
@ -171,7 +161,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){
},
init: function () {
this._super.apply(this, arguments);
this.action_manager = this.findAncestor(function(ancestor){
this.action_manager = this.findAncestor(function (ancestor) {
return ancestor instanceof ActionManager;
});
},

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<t t-name="mail.chat.SidebarFailed">
<span class="o_mail_failed_message_refresh hidden"><i class="fa fa-refresh"/> Outdated</span>
<span t-attf-class="o_mail_sidebar_failed badge #{(!counter ? 'hide' : '')}">
<t t-esc="counter"/>
</span>
</t>
<t t-extend="mail.chat.Sidebar">
<t t-jquery="div[class='o_mail_chat_sidebar']>hr[class='mb8']" t-operation="before">
<div t-attf-class="o_mail_chat_title_main o_mail_chat_channel_item #{(active_channel_id === 'channel_failed') ? 'o_active': ''}"
data-channel-id="channel_failed">
<span class="o_channel_name"><i class="fa fa-exclamation mr8"/>Failed</span>
<t t-set="counter" t-value="failed_counter"/>
<t t-call="mail.chat.SidebarFailed"/>
</div>
</t>
</t>
<t t-extend="mail.EmptyChannel">
<t t-jquery="t:last-child" t-operation="after">
<t t-if="options.channel_id==='channel_failed'">
<div class="o_thread_title">Congratulations, doesn't have failed messages</div>
<div>Failed messages appear here.</div>
</t>
</t>
</t>
</template>

View File

@ -1,63 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail_tracking.failed_message">
<div class="o_thread_message" style="margin-bottom: 10px">
<div class="o_thread_message_sidebar">
<div class="o_avatar_stack">
<img t-attf-src="/web/image#{message.author_id[0] >= 0 ? ('/res.partner/' + message.author_id[0] + '/image_small') : ''}" class="o_thread_message_avatar img-circle mb8" t-att-title="message.author_id[1]"/>
<i class="o_avatar_icon fa fa-exclamation bg-danger-full" title="Failed"></i>
</div>
</div>
<div class="o_thread_message_core">
<div class="o_mail_info">
<strong class="o_thread_author">
<t t-esc="message.author_id[1]"/>
</strong>
- <small class="o_mail_timestamp" t-att-title="message.date.format(date_format)"><t t-esc="message.hour"/></small><br/>
<strong class="text-danger">Failed Recipients:</strong>
<t t-set="first_recipient" t-value="true"/>
<t t-foreach="message.failed_recipients" t-as="recipient">
<t t-if="!first_recipient">
-
</t>
<a class="o_mail_action_tracking_partner"
t-att-data-partner="recipient[0]"
t-attf-href="#model=res.partner&amp;id=#{recipient[0]}">
<t t-esc="recipient[1]"/>
</a>
<t t-set="first_recipient" t-value="false"/>
</t>
</div>
<div class="o_thread_message_note small">
<t t-raw="message.body"/>
</div>
<div class="o_thread_message_tools btn-group">
<a href="#" class="btn btn-link btn-success text-muted btn-sm o_failed_message_reviewed o_activity_link mr8" t-att-data-message-id="message.id">
<i class="fa fa-check"/> Mark Reviewed
</a>
<a href="#" class="btn btn-link btn-default text-muted btn-sm o_failed_message_retry" t-att-data-message-id="message.id">
<i class="fa fa-retweet"/> Retry
</a>
</div>
</div>
</div>
</t>
<t t-name="mail_tracking.failed_message_items">
<div class="o_thread_date_separator o_border_dashed collapsed" data-toggle="collapse" data-target="#o_chatter_failed_message">
<span class="o_thread_date btn">
<i class="fa fa-fw fa-caret-down"/>
Failed messages
<small class="o_chatter_failed_message_summary ml8">
<span class="label img-circle label-danger"><t t-esc="nbFailedMessages"/></span>
</small>
</span>
</div>
<div id="o_chatter_failed_message" class="collapse">
<t t-foreach="failed_messages" t-as="message">
<t t-call="mail_tracking.failed_message" />
</t>
</div>
</t>
</templates>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-extend="mail.widget.Thread.Message">
<t t-jquery="span[t-attf-class=o_thread_icons]" t-operation="append">
<a t-if="message.isFailed() &amp;&amp; options.displayRetryButton" class="btn btn-link o_thread_icon btn-default text-muted btn-sm o_failed_message_reviewed o_activity_link mr8" t-att-data-message-id="message.getID()">
<i class="fa fa-check"/> Set as Reviewed
</a>
<a t-if="message.isFailed() &amp;&amp; options.displayReviewedButton" class="btn btn-link o_thread_icon btn-default text-muted btn-sm o_failed_message_retry" t-att-data-message-id="message.getID()">
<i class="fa fa-retweet"/> Retry
</a>
</t>
</t>
</templates>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="mail_tracking.SidebarFailedCounter">
<span t-attf-class="o_mail_sidebar_failed badge badge-pill #{(counter ? '' : 'd-none')}">
<t t-esc="counter"/>
</span>
</t>
<t t-name="mail_tracking.SidebarFailed">
<div t-attf-class="o_mail_discuss_title_main o_mail_discuss_item #{(activeThreadID === 'mailbox_failed') ? 'o_active': ''}"
data-thread-id="mailbox_failed">
<span class="o_thread_name"><i class="fa fa-exclamation mr8"/>Failed</span>
<t t-set="counter" t-value="failedCounter"/>
<t t-call="mail_tracking.SidebarFailedCounter"/>
</div>
</t>
<t t-extend="mail.widget.Thread.Empty">
<t t-jquery="t:last-child" t-operation="after">
<t t-if="thread.getID() === 'mailbox_failed'">
<div class="o_thread_title">Congratulations, you don't have any failed messages</div>
<div>Failed messages appear here.</div>
</t>
</t>
</t>
<t t-extend="mail.discuss.ControlButtons">
<t t-jquery="div" t-operation="append">
<button type="button" class="btn btn-secondary o_mail_discuss_button_set_all_reviewed d-none d-md-none d-md-inline-block" title="Mark all as reviewed">Set all as reviewed</button>
</t>
</t>
</templates>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail_tracking.failed_message_items">
<div class="o_thread_date_separator o_border_dashed collapsed" data-toggle="collapse" data-target="#o_chatter_failed_message">
<a role="button" class="o_thread_date btn">
<i class="fa fa-fw fa-caret-down"/>
Failed messages
<small class="o_chatter_failed_message_summary ml8">
<span class="badge rounded-circle badge-danger"><t t-esc="nbFailedMessages"/></span>
</small>
</a>
</div>
<div id="o_chatter_failed_message" class="in collapse">
<t t-foreach="failed_messages" t-as="message">
<div class="o_thread_message" style="margin-bottom: 10px">
<div class="o_thread_message_sidebar">
<div class="o_avatar_stack">
<img t-attf-src="/web/image/res.partner/#{message.author[0]}/image_small" class="o_thread_message_avatar rounded-circle mb8" t-att-title="message.author[1]" t-att-alt="message.author[1]"/>
<i t-att-class="'o_avatar_icon fa fa-exclamation bg-danger-full'"
title="Failed"/>
</div>
</div>
<div class="o_thread_message_core">
<div class="o_mail_info text-muted">
<strong class="o_thread_author">
<t t-esc="message.author[1]"/>
</strong>
- <small class="o_mail_timestamp" t-att-title="message.date.format(date_format)"><t t-esc="message.hour"/></small>
<span t-attf-class="o_thread_icons">
<a href="#" class="btn btn-link btn-success o_thread_icon text-muted btn-sm o_failed_message_reviewed o_activity_link" t-att-data-message-id="message.id">
<i class="fa fa-check"/> Set as Reviewed
</a>
<a href="#" class="btn btn-link btn-default o_thread_icon text-muted btn-sm o_failed_message_retry" t-att-data-message-id="message.id">
<i class="fa fa-retweet"/> Retry
</a>
</span>
<br/>
<strong class="text-danger">Failed Recipients:</strong>
<t t-foreach="message.failed_recipients" t-as="recipient">
<t t-if="!recipient_first">
-
</t>
<a class="o_mail_action_tracking_partner"
t-att-data-partner="recipient[0]"
t-attf-href="#model=res.partner&amp;id=#{recipient[0]}">
<t t-esc="recipient[1]"/>
</a>
</t>
</div>
<div class="o_thread_message_note small">
<t t-raw="message.body"/>
</div>
</div>
</div>
</t>
</div>
</t>
</templates>

View File

@ -22,8 +22,9 @@
</t>
<t t-elif="tracking['status'] === 'error'">
<span class="mail_tracking_error mail_tracking_pointer">
<i class="fa fa-remove"></i>
</span>
<i t-if="tracking['error_type'] === 'no_recipient'" class="fa fa-user-times"></i>
<i t-else="" class="fa fa-remove"></i>
</span>
</t>
<t t-elif="tracking['status'] === 'sent'">
<span class="mail_tracking_sent mail_tracking_pointer">
@ -51,7 +52,7 @@
<t t-if="message.hasPartnerTrackings() || message.hasEmailCc()" >
<p class="o_mail_tracking">
<strong>To:</strong>
<t t-foreach="message.partner_trackings" t-as="tracking">
<t t-foreach="message.getPartnerTrackings()" t-as="tracking">
<t t-if="!tracking_first">
-
</t>
@ -65,9 +66,11 @@
<t t-else="">
<span t-attf-class="#{tracking['isCc'] ? 'o_mail_cc' : ''}"><t t-esc="tracking['recipient']"/></span>
</t>
<t t-if="tracking['status'] === 'error' &amp;&amp; tracking['error_type'] === 'no_recipient'" t-set="title_status" t-value="tracking['error_description']" />
<t t-else="" t-set="title_status" t-value="tracking['status_human']" />
<span class="mail_tracking o_mail_action_tracking_status"
t-att-data-tracking="tracking['tracking_id']"
t-att-title="tracking['status_human']">
t-att-title="title_status">
<t t-call="mail.tracking.status"/>
</span>
</t>

View File

@ -5,6 +5,8 @@ import mock
from odoo.tools import mute_logger
import time
import base64
import psycopg2
import psycopg2.errorcodes
from odoo import http
from odoo.tests.common import TransactionCase
from ..controllers.main import MailTrackingController, BLANK
@ -36,8 +38,9 @@ class TestMailTracking(TransactionCase):
})
self.last_request = http.request
http.request = type('obj', (object,), {
'db': self.env.cr.dbname,
'env': self.env,
'cr': self.env.cr,
'db': self.env.cr.dbname,
'endpoint': type('obj', (object,), {
'routing': [],
}),
@ -116,6 +119,30 @@ class TestMailTracking(TransactionCase):
tracking_email.event_create('open', metadata)
self.assertEqual(tracking_email.state, 'opened')
def test_message_post_partner_no_email(self):
# Create message with recipient without defined email
self.recipient.write({'email': False})
message = self.env['mail.message'].create({
'subject': 'Message test',
'author_id': self.sender.id,
'email_from': self.sender.email,
'message_type': 'comment',
'model': 'res.partner',
'res_id': self.recipient.id,
'partner_ids': [(4, self.recipient.id)],
'body': '<p>This is a test message</p>',
})
message._notify(message, {}, force_send=True)
# Search tracking created
tracking_email = self.env['mail.tracking.email'].search([
('mail_message_id', '=', message.id),
('partner_id', '=', self.recipient.id),
])
# No email should generate a error state: no_recipient
self.assertEqual(tracking_email.state, 'error')
self.assertEqual(tracking_email.error_type, 'no_recipient')
self.assertFalse(self.recipient.email_bounced)
def _check_partner_trackings(self, message):
message_dict = message.message_format()[0]
self.assertEqual(len(message_dict['partner_trackings']), 3)
@ -136,18 +163,15 @@ class TestMailTracking(TransactionCase):
self.assertTrue(foundNoPartner)
def test_email_cc(self):
message = self.env['mail.message'].create({
'subject': 'Message test',
'author_id': self.sender.id,
'email_from': self.sender.email,
'message_type': 'comment',
'model': 'res.partner',
'res_id': self.recipient.id,
'partner_ids': [(4, self.recipient.id)],
'email_cc': 'unnamed@test.com, sender@example.com',
'body': '<p>This is a test message</p>',
sender_user = self.env['res.users'].create({
'name': 'Sender User Test',
'partner_id': self.sender.id,
'login': 'sender-test',
})
self._check_partner_trackings(message)
message = self.recipient.sudo(user=sender_user).message_post(
body='<p>This is a test message</p>',
cc='unnamed@test.com, sender@example.com'
)
# suggested recipients
recipients = self.recipient.message_get_suggested_recipients()
suggested_mails = {
@ -168,31 +192,37 @@ class TestMailTracking(TransactionCase):
', recipient@example.com',
'body': '<p>This is another test message</p>',
})
message._notify(message, {}, force_send=True)
recipients = self.recipient.message_get_suggested_recipients()
self.assertEqual(len(recipients[self.recipient.id][0]), 3)
self._check_partner_trackings(message)
def test_failed_message(self):
MailMessageObj = self.env['mail.message']
# Create message
mail, tracking = self.mail_send(self.recipient.email)
self.assertFalse(tracking.mail_message_id.mail_tracking_needs_action)
# Force error state
tracking.state = 'error'
self.assertTrue(tracking.mail_message_id.mail_tracking_needs_action)
failed_count = self.env['mail.message'].get_failed_count()
failed_count = MailMessageObj.get_failed_count()
self.assertTrue(failed_count > 0)
values = tracking.mail_message_id.get_failed_messages()
self.assertEqual(values[0]['id'], tracking.mail_message_id.id)
messages = self.env['mail.message'].message_fetch([])
messages_failed = self.env['mail.message'].with_context(
filter_failed_message=True).message_fetch([])
messages = MailMessageObj.search([])
messages_failed = MailMessageObj.search(
MailMessageObj._get_failed_message_domain())
self.assertTrue(messages)
self.assertTrue(messages_failed)
self.assertTrue(len(messages) > len(messages_failed))
tracking.mail_message_id.toggle_tracking_status()
self.assertFalse(tracking.mail_message_id.mail_tracking_needs_action)
self.assertTrue(
self.env['mail.message'].get_failed_count() < failed_count)
MailMessageObj.get_failed_count() < failed_count)
# No author_id
tracking.mail_message_id.author_id = False
values = tracking.mail_message_id.get_failed_messages()[0]
self.assertEqual(values['author'][0], -1)
def mail_send(self, recipient):
mail = self.env['mail.mail'].create({
@ -215,15 +245,52 @@ class TestMailTracking(TransactionCase):
mail, tracking = self.mail_send(self.recipient.email)
self.assertEqual(mail.email_to, tracking.recipient)
self.assertEqual(mail.email_from, tracking.sender)
res = controller.mail_tracking_open(db, tracking.id)
self.assertEqual(image, res.response[0])
# Two events: sent and open
self.assertEqual(2, len(tracking.tracking_event_ids))
# Fake event: tracking_email_id = False
res = controller.mail_tracking_open(db, False)
self.assertEqual(image, res.response[0])
# Two events again because no tracking_email_id found for False
self.assertEqual(2, len(tracking.tracking_event_ids))
with mock.patch('odoo.http.db_filter') as mock_client:
mock_client.return_value = True
res = controller.mail_tracking_open(
db, tracking.id, tracking.token)
self.assertEqual(image, res.response[0])
# Two events: sent and open
self.assertEqual(2, len(tracking.tracking_event_ids))
# Fake event: tracking_email_id = False
res = controller.mail_tracking_open(db, False, False)
self.assertEqual(image, res.response[0])
# Two events again because no tracking_email_id found for False
self.assertEqual(2, len(tracking.tracking_event_ids))
def test_mail_tracking_open(self):
controller = MailTrackingController()
db = self.env.cr.dbname
with mock.patch('odoo.http.db_filter') as mock_client:
mock_client.return_value = True
mail, tracking = self.mail_send(self.recipient.email)
# Tracking is in sent or delivered state. But no token give.
# Don't generates tracking event
controller.mail_tracking_open(db, tracking.id)
self.assertEqual(1, len(tracking.tracking_event_ids))
tracking.write({'state': 'opened'})
# Tracking isn't in sent or delivered state.
# Don't generates tracking event
controller.mail_tracking_open(db, tracking.id, tracking.token)
self.assertEqual(1, len(tracking.tracking_event_ids))
tracking.write({'state': 'sent'})
# Tracking is in sent or delivered state and a token is given.
# Generates tracking event
controller.mail_tracking_open(db, tracking.id, tracking.token)
self.assertEqual(2, len(tracking.tracking_event_ids))
# Generate new email due concurrent event filter
mail, tracking = self.mail_send(self.recipient.email)
tracking.write({'token': False})
# Tracking is in sent or delivered state but a token is given for a
# record that doesn't have a token.
# Don't generates tracking event
controller.mail_tracking_open(db, tracking.id, 'tokentest')
self.assertEqual(1, len(tracking.tracking_event_ids))
# Tracking is in sent or delivered state and not token is given for
# a record that doesn't have a token.
# Generates tracking event
controller.mail_tracking_open(db, tracking.id, False)
self.assertEqual(2, len(tracking.tracking_event_ids))
def test_concurrent_open(self):
mail, tracking = self.mail_send(self.recipient.email)
@ -400,12 +467,14 @@ class TestMailTracking(TransactionCase):
def test_db(self):
db = self.env.cr.dbname
controller = MailTrackingController()
not_found = controller.mail_tracking_all('not_found_db')
self.assertEqual(b'NOT FOUND', not_found.response[0])
none = controller.mail_tracking_all(db)
self.assertEqual(b'NONE', none.response[0])
none = controller.mail_tracking_event(db, 'open')
self.assertEqual(b'NONE', none.response[0])
with mock.patch('odoo.http.db_filter') as mock_client:
mock_client.return_value = True
with self.assertRaises(psycopg2.OperationalError):
controller.mail_tracking_event('not_found_db')
none = controller.mail_tracking_event(db)
self.assertEqual(b'NONE', none.response[0])
none = controller.mail_tracking_event(db, 'open')
self.assertEqual(b'NONE', none.response[0])
class TestMailTrackingViews(TransactionCase):

View File

@ -7,13 +7,15 @@
inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet"
href="/mail_tracking/static/src/css/mail_tracking.less"/>
href="/mail_tracking/static/src/css/mail_tracking.scss"/>
<link rel="stylesheet"
href="/mail_tracking/static/src/css/failed_message.less"/>
href="/mail_tracking/static/src/css/failed_message.scss"/>
<script type="text/javascript"
src="/mail_tracking/static/src/js/mail_tracking.js"/>
<script type="text/javascript"
src="/mail_tracking/static/src/js/failed_message.js"/>
src="/mail_tracking/static/src/js/failed_message/discuss.js"/>
<script type="text/javascript"
src="/mail_tracking/static/src/js/failed_message/thread.js"/>
</xpath>
</template>
</odoo>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record model="ir.ui.view" id="email_compose_message_wizard_form">
<field name="model">mail.compose.message</field>
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
<field name="arch" type="xml">
<field name="active_domain" position="after">
<field name="hide_followers" invisible="1" />
</field>
<xpath expr="//div[@groups='base.group_user']/span[2]" position="attributes">
<attribute name="attrs">{'invisible':['|', '|', ('model', '=', False), ('composition_mode', '=', 'mass_mail'), ('hide_followers', '=', True)]}</attribute>
</xpath>
</field>
</record>
</data>
</odoo>