diff --git a/mail_forward/README.rst b/mail_forward/README.rst new file mode 100644 index 00000000..83f998b7 --- /dev/null +++ b/mail_forward/README.rst @@ -0,0 +1,101 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +=============== +Message forward +=============== + +This module was written to extend the functionality of mails to support +forwarding them to another contact or database object. + +* **To another object of the database:** All its followers are notified + according to their settings. + +* **To other contacts:** They recieve the forwarded message according to the + usual notification rules of Odoo. + +Configuration +============= + +When installed, this module allows forwarding to a limited selection of +database models, that will get automatically updated when you install other +modules. + +This list can be customized by a user that has *Technical Features* permission. +To do it: + +* Go to *Settings > Technical > Database Structure > Referenceable Models*. + +* Any model there with *Mail forward target* enabled will appear in the list of + models that can get forwarded messages. + + * If you want to *remove* a model from the list, it's usually better to just + *disable* its check box instead of deleting it, because that record might + be used by other modules. + +Usage +===== + +To use this module, you need to: + +* Go to any view that has a message thread. +* Press *Forward* (the button with a curved arrow pointing to the right). +* Modify the subject if you wish. +* If you want to forward the message to an *object*, choose it from the + selector. +* If you want to forward the message to some partner(s), add them in the + selector. +* Modify the message if you want. +* Add attachments if you want. +* If the message contains attachments, you can turn on the *Move attachments* + option, and those will get attached to the new object. + + You will need to install the *document* module and have at least *User* + permissions for *Knowledge* to see the effect. + +.. info:: + Technical note: Internally, forwarded messages are modified copies of + original ones, but attachments are references to the exact originals to + avoid duplicating disk space. Keep that in mind when managing permissions. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/205/8.0 + +For further information, please visit: + +* https://www.odoo.com/forum/help-1 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. In +case of trouble, please check there if your issue has already been reported. If +you spotted it first, help us smashing it by providing a detailed and welcomed +feedback `here +`_. + + +Credits +======= + +Contributors +------------ + +* Jairo Llopis + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/mail_forward/__init__.py b/mail_forward/__init__.py new file mode 100644 index 00000000..65738b52 --- /dev/null +++ b/mail_forward/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2014-2015 Grupo ESOC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/mail_forward/__openerp__.py b/mail_forward/__openerp__.py new file mode 100644 index 00000000..302a8e1f --- /dev/null +++ b/mail_forward/__openerp__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# © 2014-2015 Grupo ESOC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Message Forward", + "summary": "Add option to forward messages", + "version": "8.0.7.0.0", + "category": "Social Network", + "website": "https://grupoesoc.es", + "author": "Grupo ESOC Ingeniería de Servicios, " + "Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "mail", + "web", + ], + "data": [ + "views/assets.xml", + "views/compose_message.xml", + "views/res_request_link.xml", + ], + "qweb": [ + "static/src/xml/mail_forward.xml", + ], +} diff --git a/mail_forward/i18n/es.po b/mail_forward/i18n/es.po new file mode 100644 index 00000000..6541e431 --- /dev/null +++ b/mail_forward/i18n/es.po @@ -0,0 +1,168 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_forward +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-11-02 09:15+0000\n" +"PO-Revision-Date: 2015-11-02 10:19+0100\n" +"Last-Translator: Jairo Llopis \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"Language: es\n" +"X-Generator: Poedit 1.8.5\n" + +#. module: mail_forward +#. openerp-web +#: code:addons/mail_forward/static/src/js/mail_forward.js:25 +#, python-format +msgid "(No subject)" +msgstr "(Sin asunto)" + +#. module: mail_forward +#: help:res.request.link,mail_forward_target:0 +msgid "Allow to forward mails to this model." +msgstr "Permitir reenviar correos a este modelo." + +#. module: mail_forward +#: help:mail_forward.compose_message,move_attachments:0 +msgid "Attachments will be assigned to the chosen destination object and you will be able to pick them from its 'Attachments' button, but they will not be there for the current object if any. In any case you can always open it from the message itself." +msgstr "Los adjuntos se asignarán al objeto de destino escogido y podrá escogerlos desde el botón 'Adjuntos', pero desaparecerán del objeto en el que estén actualmente (si están en alguno). En cualquier caso, siempre podrá abrirlos desde el propio mensaje." + +#. module: mail_forward +#. openerp-web +#: code:addons/mail_forward/static/src/js/mail_forward.js:47 +#, python-format +msgid "CC: " +msgstr "CC: " + +#. module: mail_forward +#: field:mail_forward.compose_message,create_uid:0 +msgid "Created by" +msgstr "Creado por" + +#. module: mail_forward +#: field:mail_forward.compose_message,create_date:0 +msgid "Created on" +msgstr "Creado el" + +#. module: mail_forward +#. openerp-web +#: code:addons/mail_forward/static/src/js/mail_forward.js:38 +#, python-format +msgid "Date: " +msgstr "Fecha: " + +#. module: mail_forward +#: field:mail_forward.compose_message,destination_object_id:0 +msgid "Destination object" +msgstr "Objeto de destino" + +#. module: mail_forward +#. openerp-web +#: code:addons/mail_forward/static/src/js/mail_forward.js:16 +#, python-format +msgid "FWD" +msgstr "RV" + +#. module: mail_forward +#. openerp-web +#: code:addons/mail_forward/static/src/xml/mail_forward.xml:10 +#, python-format +msgid "Forward" +msgstr "Reenviar" + +#. module: mail_forward +#: model:ir.actions.act_window,name:mail_forward.compose_action +msgid "Forward Email" +msgstr "Reenviar correo electrónico" + +#. module: mail_forward +#. openerp-web +#: code:addons/mail_forward/static/src/js/mail_forward.js:36 +#, python-format +msgid "Forwarded message" +msgstr "Mensaje reenviado" + +#. module: mail_forward +#. openerp-web +#: code:addons/mail_forward/static/src/js/mail_forward.js:37 +#, python-format +msgid "From: " +msgstr "De: " + +#. module: mail_forward +#: field:mail_forward.compose_message,id:0 +msgid "ID" +msgstr "ID" + +#. module: mail_forward +#: field:mail_forward.compose_message,write_uid:0 +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: mail_forward +#: field:mail_forward.compose_message,write_date:0 +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: mail_forward +#: field:res.request.link,mail_forward_target:0 +msgid "Mail forward target" +msgstr "Objetivo al reenviar" + +#. module: mail_forward +#: field:mail_forward.compose_message,move_attachments:0 +msgid "Move attachments" +msgstr "Mover adjuntos" + +#. module: mail_forward +#: help:mail_forward.compose_message,destination_object_id:0 +msgid "Object where the forwarded message will be attached" +msgstr "Objeto al que se añadirá el mensaje reenviado" + +#. module: mail_forward +#: field:mail_forward.compose_message,original_wizard_id:0 +msgid "Original message compose wizard" +msgstr "Asistente original de composición de mensajes" + +#. module: mail_forward +#. openerp-web +#: code:addons/mail_forward/static/src/js/mail_forward.js:41 +#, python-format +msgid "Subject: " +msgstr "Asunto: " + +#. module: mail_forward +#. openerp-web +#: code:addons/mail_forward/static/src/js/mail_forward.js:44 +#, python-format +msgid "To: " +msgstr "Para: " + +#. module: mail_forward +#: view:mail_forward.compose_message:mail_forward.compose_form +msgid "" +"{\n" +" 'invisible': [('destination_object_id', '=', False)]\n" +" }" +msgstr "" +"{\n" +" 'invisible': [('destination_object_id', '=', False)]\n" +" }" + +#. module: mail_forward +#: view:mail_forward.compose_message:mail_forward.compose_form +msgid "" +"{\n" +" 'required': [('destination_object_id', '=', False)]\n" +" }" +msgstr "" +"{\n" +" 'required': [('destination_object_id', '=', False)]\n" +" }" diff --git a/mail_forward/models/__init__.py b/mail_forward/models/__init__.py new file mode 100644 index 00000000..3faf4bfa --- /dev/null +++ b/mail_forward/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2014-2015 Grupo ESOC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import compose_message, res_request_link diff --git a/mail_forward/models/compose_message.py b/mail_forward/models/compose_message.py new file mode 100644 index 00000000..d6511e4d --- /dev/null +++ b/mail_forward/models/compose_message.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# © 2014-2015 Grupo ESOC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import api, fields, models + + +class MailForwardComposeMessage(models.TransientModel): + """Allow forwarding a message. + + It duplicates the message and optionally attaches it to another object + of the database and sends it to another recipients than the original one. + """ + _name = "mail_forward.compose_message" + _inherits = {"mail.compose.message": "original_wizard_id"} + + @api.model + def default_get(self, fields): + """Fix default values. + + Sometimes :meth:`openerp.addons.mail.mail_compose_message + .mail_compose_message.default_get` overwrites the default value + for the ``subject`` field, even when it gets the right default value + from the context. + + This method fixes that by getting it from the context if available. + """ + result = self.original_wizard_id.default_get(fields) + + if "subject" in result and "default_subject" in self.env.context: + result["subject"] = self.env.context["default_subject"] + + return result + + @api.model + def _get_model_selection(self): + """Get allowed models and their names.""" + model_objs = self.env["res.request.link"].search( + [("mail_forward_target", "=", True)], + order="name") + return [(m.object, m.name) for m in model_objs] + + @api.one + @api.onchange("destination_object_id") + def change_destination_object(self): + """Update some fields for the new message.""" + if self.destination_object_id: + self.model = self.destination_object_id._name + self.res_id = self.destination_object_id.id + + model_name = (self.env["ir.model"] + .search([("model", "=", self.model)]) + .name) + record_name = self.destination_object_id.name_get()[0][1] + if model_name: + record_name = "%s %s" % (model_name, record_name) + + self.record_name = record_name + else: + self.model = self.res_id = self.record_name = False + + @api.one + def send_mail(self): + """Send mail and execute the attachment relocation if needed.""" + # Let the original wizard do de hard work + result = self.original_wizard_id.send_mail() + + # Relocate attachments if needed + if (self.move_attachments and + self.model and + self.res_id and + self.attachment_ids): + for attachment in self.attachment_ids: + attachment.res_model = self.model + attachment.res_id = self.res_id + + return result + + destination_object_id = fields.Reference( + _get_model_selection, + "Destination object", + help="Object where the forwarded message will be attached") + + move_attachments = fields.Boolean( + "Move attachments", + help="Attachments will be assigned to the chosen destination " + "object and you will be able to pick them from its " + "'Attachments' button, but they will not be there for " + "the current object if any. In any case you can always " + "open it from the message itself.") + + original_wizard_id = fields.Many2one( + "mail.compose.message", + "Original message compose wizard", + delegate=True, + ondelete="cascade", + required=True) diff --git a/mail_forward/models/res_request_link.py b/mail_forward/models/res_request_link.py new file mode 100644 index 00000000..e5d4dc7a --- /dev/null +++ b/mail_forward/models/res_request_link.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# © 2014-2015 Grupo ESOC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import fields, models + + +class ResRequestLink(models.Model): + _inherit = "res.request.link" + + mail_forward_target = fields.Boolean( + default=True, + index=True, + help="Allow to forward mails to this model.") diff --git a/mail_forward/static/description/icon.png b/mail_forward/static/description/icon.png new file mode 100644 index 00000000..d3c09dc0 Binary files /dev/null and b/mail_forward/static/description/icon.png differ diff --git a/mail_forward/static/src/css/mail_forward.css b/mail_forward/static/src/css/mail_forward.css new file mode 100644 index 00000000..6eabe491 --- /dev/null +++ b/mail_forward/static/src/css/mail_forward.css @@ -0,0 +1,12 @@ +/* © 2014-2015 Grupo ESOC + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + */ + +.openerp .oe_mail .oe_msg .oe_msg_icons .oe_forward:hover a { + color: #1FC0FF; + text-shadow: 0px 1px #184FC5, + 0px -1px #184FC5, + -1px 0px #184FC5, + 1px 0px #184FC5, + 0px 3px 3px rgba(0, 0, 0, 0.1); +} diff --git a/mail_forward/static/src/js/mail_forward.js b/mail_forward/static/src/js/mail_forward.js new file mode 100644 index 00000000..2629a435 --- /dev/null +++ b/mail_forward/static/src/js/mail_forward.js @@ -0,0 +1,77 @@ +/* © 2014-2015 Grupo ESOC + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + */ + +"use strict"; +openerp.mail_forward = function (instance) { + var _t = instance.web._t; + instance.mail.ThreadMessage.include({ + bind_events: function () { + this._super.apply(this, arguments); + this.$('.oe_forward').on('click', this.on_message_forward); + }, + + on_message_forward: function () { + // Generate email subject as possible from record_name and subject + var subject = [_t("FWD")]; + if (this.record_name && (this.show_record_name || + this.parent_id)) + { + subject.push(this.record_name); + } + if (this.subject) { + subject.push(this.subject); + } else if (subject.length < 2) { + subject.push(_t("(No subject)")); + } + + // Get only ID from the attachments + var attachment_ids = []; + for (var n in this.attachment_ids) { + attachment_ids.push(this.attachment_ids[n].id); + } + + // Get necessary fields from the forwarded message + var header = [ + "----------" + _t("Forwarded message") + "----------", + _t("From: ") + this.author_id[1], + _t("Date: ") + this.date, + ]; + if (this.subject) { + header.push(_t("Subject: ") + this.subject); + } + if (this.email_to) { + header.push(_t("To: ") + this.email_to); + } + if (this.email_cc) { + header.push(_t("CC: ") + this.email_cc); + } + header = header.map(_.str.escapeHTML).join("
") + + var context = { + default_attachment_ids: attachment_ids, + default_body: + "

" + header + "


" + + this.body, + default_model: this.model, + default_res_id: this.res_id, + default_subject: subject.join(": "), + }; + + if (this.model && this.res_id) { + context.default_destination_object_id = + [this.model, this.res_id].join(); + } + + // Get the action data and execute it to open the composer wizard + var do_action = this.do_action; + this.rpc("/web/action/load", { + "action_id": "mail_forward.compose_action", + }) + .done(function(action) { + action.context = context; + do_action(action); + }); + } + }); +}; diff --git a/mail_forward/static/src/xml/mail_forward.xml b/mail_forward/static/src/xml/mail_forward.xml new file mode 100644 index 00000000..a3c97daa --- /dev/null +++ b/mail_forward/static/src/xml/mail_forward.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/mail_forward/tests/__init__.py b/mail_forward/tests/__init__.py new file mode 100644 index 00000000..2a1e976b --- /dev/null +++ b/mail_forward/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2014-2015 Grupo ESOC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_compose_message diff --git a/mail_forward/tests/test_compose_message.py b/mail_forward/tests/test_compose_message.py new file mode 100644 index 00000000..d331cf91 --- /dev/null +++ b/mail_forward/tests/test_compose_message.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# © 2014-2015 Grupo ESOC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from os import path +from openerp.tests.common import TransactionCase + + +class ForwardMailCase(TransactionCase): + def setUp(self, *args, **kwargs): + super(ForwardMailCase, self).setUp(*args, **kwargs) + self.compose = self.env["mail.compose.message"].create({}) + self.fwd = self.env["mail_forward.compose_message"].create({ + "original_wizard_id": self.compose.id, + }) + self.partner = self.env["res.partner"].create({"name": __file__}) + self.attachment = self.env["ir.attachment"].create({ + "name": "Testing source", + "datas_fname": path.basename(__file__), + "type": "url", + "url": "file://%s" % __file__, + }) + + def test_subject(self): + """Test correct subject is used.""" + self.compose.subject = "Bad subject" + good = "Good subject" + defaults = self.fwd.with_context(default_subject=good).default_get( + ["subject"]) + self.assertEqual(defaults["subject"], good) + + def test_change_destination(self): + """Test what happens when changing the destination.""" + model = self.env["ir.model"].search( + [("model", "=", self.partner._name)]) + + # Set a partner as destination object + self.fwd.destination_object_id = self.partner + self.fwd.change_destination_object() + + self.assertEqual(self.fwd.model, self.partner._name) + self.assertEqual(self.fwd.res_id, self.partner.id) + self.assertEqual(self.fwd.record_name, + "%s %s" % (model.name, self.partner.name)) + + # Remove the destination object + self.fwd.destination_object_id = False + self.fwd.change_destination_object() + + self.assertFalse(self.fwd.model) + self.assertFalse(self.fwd.res_id) + self.assertFalse(self.fwd.record_name) + + def test_move_attachments(self): + """Attachments moved correctly.""" + self.fwd.attachment_ids |= self.attachment + self.fwd.destination_object_id = self.partner + self.fwd.change_destination_object() + self.fwd.body = "body" + self.fwd.subject = "subject" + self.fwd.move_attachments = True + self.fwd.send_mail() + + self.assertEqual(self.attachment.res_model, self.partner._name) + self.assertEqual(self.attachment.res_id, self.partner.id) diff --git a/mail_forward/views/assets.xml b/mail_forward/views/assets.xml new file mode 100644 index 00000000..7c04b70d --- /dev/null +++ b/mail_forward/views/assets.xml @@ -0,0 +1,21 @@ + + + + + + + +