diff --git a/.travis.yml b/.travis.yml index 8b1715f..116bb8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,3 +32,6 @@ script: after_success: coveralls + +notifications: + email: false diff --git a/mail_all/doc/changelog.rst b/mail_all/doc/changelog.rst index e2b0277..5f35720 100644 --- a/mail_all/doc/changelog.rst +++ b/mail_all/doc/changelog.rst @@ -1,5 +1,5 @@ -Changelog -========= +Updates +======= `1.0.0` ------- diff --git a/mail_check_immediately/doc/changelog.rst b/mail_check_immediately/doc/changelog.rst index e72d492..013b693 100644 --- a/mail_check_immediately/doc/changelog.rst +++ b/mail_check_immediately/doc/changelog.rst @@ -1,7 +1,7 @@ .. _changelog: -Changelog -========= +Updates +======= `1.0.1` ------- diff --git a/mail_fix_553/mail_fix_553.py b/mail_fix_553/mail_fix_553.py index 76ad7dc..003af81 100644 --- a/mail_fix_553/mail_fix_553.py +++ b/mail_fix_553/mail_fix_553.py @@ -15,8 +15,7 @@ from openerp.tools.translate import _ _logger = logging.getLogger(__name__) - -class mail_mail(osv.Model): +class MailMail(osv.Model): _inherit = "mail.mail" def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): @@ -41,7 +40,7 @@ class mail_mail(osv.Model): catchall_alias_name = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.name_alias_from", context=context) catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context) - correct_email_from = '@%s>?\s*$' % catchall_domain + correct_email_from = r'@%s>?\s*$' % catchall_domain default_email_from = '%s@%s' % (catchall_alias, catchall_domain) context = dict(context or {}) diff --git a/mail_fix_empty_body/models.py b/mail_fix_empty_body/models.py index 1878c30..cf622d3 100644 --- a/mail_fix_empty_body/models.py +++ b/mail_fix_empty_body/models.py @@ -2,12 +2,11 @@ from openerp import models - -class mail_compose_message(models.TransientModel): +class MailComposeMessage(models.TransientModel): _inherit = 'mail.compose.message' def get_mail_values(self, cr, uid, wizard, res_ids, context=None): - res = super(mail_compose_message, self).get_mail_values(cr, uid, wizard, res_ids, context) + res = super(MailComposeMessage, self).get_mail_values(cr, uid, wizard, res_ids, context) for id, d in res.iteritems(): d['body'] = d.get('body') or '' return res diff --git a/mail_move_message/doc/changelog.rst b/mail_move_message/doc/changelog.rst index 4704b1f..2f27ae2 100644 --- a/mail_move_message/doc/changelog.rst +++ b/mail_move_message/doc/changelog.rst @@ -1,7 +1,7 @@ .. _changelog: -Changelog -========= +Updates +======= `1.0.4` ------- diff --git a/mail_move_message/mail_move_message_models.py b/mail_move_message/mail_move_message_models.py index 73138ac..a7b9d83 100644 --- a/mail_move_message/mail_move_message_models.py +++ b/mail_move_message/mail_move_message_models.py @@ -4,7 +4,7 @@ from openerp.tools import email_split from openerp.tools.translate import _ -class wizard(models.TransientModel): +class Wizard(models.TransientModel): _name = 'mail_move_message.wizard' def _model_selection(self): @@ -26,7 +26,7 @@ class wizard(models.TransientModel): @api.model def default_get(self, fields_list): - res = super(wizard, self).default_get(fields_list) + res = super(Wizard, self).default_get(fields_list) model_fields = self.fields_get() if model_fields['model']['selection']: @@ -236,7 +236,7 @@ class wizard(models.TransientModel): return {'type': 'ir.actions.act_window_close'} -class mail_message(models.Model): +class MailMessage(models.Model): _inherit = 'mail.message' is_moved = fields.Boolean('Is moved') @@ -308,7 +308,7 @@ class mail_message(models.Model): r_vals['parent_id'] = r.moved_from_parent_id.id r_vals['res_id'] = r.moved_from_res_id r_vals['model'] = r.moved_from_model - print 'update message', r, r_vals + # print 'update message', r, r_vals if move_followers: r.sudo().move_followers(r_vals.get('model'), r_vals.get('res_id')) r.sudo().write(r_vals) @@ -329,7 +329,7 @@ class mail_message(models.Model): def name_get(self, cr, uid, ids, context=None): if not (context or {}).get('extended_name'): - return super(mail_message, self).name_get(cr, uid, ids, context=context) + return super(MailMessage, self).name_get(cr, uid, ids, context=context) if isinstance(ids, (list, tuple)) and not len(ids): return [] if isinstance(ids, (long, int)): @@ -343,13 +343,13 @@ class mail_message(models.Model): return res def _message_read_dict(self, cr, uid, message, parent_id=False, context=None): - res = super(mail_message, self)._message_read_dict(cr, uid, message, parent_id, context) + res = super(MailMessage, self)._message_read_dict(cr, uid, message, parent_id, context) res['is_moved'] = message.is_moved return res @api.multi def message_format(self): - message_values = super(mail_message, self).message_format() + message_values = super(MailMessage, self).message_format() message_index = {message['id']: message for message in message_values} for item in self: msg = message_index.get(item.id) @@ -358,7 +358,7 @@ class mail_message(models.Model): return message_values -class mail_move_message_configuration(models.TransientModel): +class MailMoveMessageConfiguration(models.TransientModel): _name = 'mail_move_message.config.settings' _inherit = 'res.config.settings' @@ -389,12 +389,12 @@ class mail_move_message_configuration(models.TransientModel): config_parameters.set_param('mail_relocation_move_followers', record.move_followers or '') -class res_partner(models.Model): +class ResPartner(models.Model): _inherit = 'res.partner' @api.model def create(self, vals): - res = super(res_partner, self).create(vals) + res = super(ResPartner, self).create(vals) if 'update_message_author' in self.env.context and 'email' in vals: mail_message_obj = self.env['mail.message'] # Escape special SQL characters in email_address to avoid invalid matches diff --git a/mail_outgoing/mail_outgoing_models.py b/mail_outgoing/mail_outgoing_models.py index 2a232dd..cd358d6 100644 --- a/mail_outgoing/mail_outgoing_models.py +++ b/mail_outgoing/mail_outgoing_models.py @@ -2,7 +2,7 @@ from openerp.osv import osv -class mail_message(osv.Model): +class MailMessage(osv.Model): _inherit = 'mail.message' def check_access_rule(self, cr, uid, ids, operation, context=None): @@ -13,10 +13,10 @@ class mail_message(osv.Model): if user_groups.issuperset(group_all_emails): return - return super(mail_message, self).check_access_rule(cr, uid, ids, operation, context) + return super(MailMessage, self).check_access_rule(cr, uid, ids, operation, context) -class mail_mail(osv.Model): +class MailMail(osv.Model): _name = 'mail.mail' _inherit = ['mail.mail', 'ir.needaction_mixin'] _needaction = True diff --git a/mail_partner_lang/models.py b/mail_partner_lang/models.py index ff364b8..d4e37a2 100644 --- a/mail_partner_lang/models.py +++ b/mail_partner_lang/models.py @@ -2,8 +2,7 @@ from openerp.osv import osv - -class mail_thread(osv.Model): +class MailThread(osv.Model): _inherit = "mail.thread" def message_track(self, cr, uid, ids, tracked_fields, initial_values, context={}): diff --git a/mail_private/README.rst b/mail_private/README.rst new file mode 100644 index 0000000..6a9fc15 --- /dev/null +++ b/mail_private/README.rst @@ -0,0 +1,29 @@ +==================== + Internal Messaging +==================== + +Send private messages to specified recipients, regardless of who are in followers list. + +Credits +======= + +Contributors +------------ +* Pavel Romanchenko + +Sponsors +-------- +* `IT-Projects LLC `__ + +Further information +=================== + +Demo: http://runbot.it-projects.info/demo/mail-addons/8.0 + +HTML Description: https://apps.odoo.com/apps/modules/8.0/mail_private/ + +Usage instructions: ``__ + +Changelog: ``__ + +Tested on Odoo 8.0 0af32f3f84bae07b11abb8538d02e35c7369a348 diff --git a/mail_private/__init__.py b/mail_private/__init__.py new file mode 100644 index 0000000..cde864b --- /dev/null +++ b/mail_private/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/mail_private/__openerp__.py b/mail_private/__openerp__.py new file mode 100644 index 0000000..445c719 --- /dev/null +++ b/mail_private/__openerp__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +{ + "name": """Internal Messaging""", + "summary": """Send private messages to specified recipients, regardless of who are in followers list.""", + "category": "Discuss", + "images": ['images/mail_private_image.png'], + "version": "1.0.0", + "application": False, + + "author": "IT-Projects LLC, Pavel Romanchenko", + "website": "https://it-projects.info", + "license": "GPL-3", + "price": 90.00, + "currency": "EUR", + + "depends": [ + "mail", + ], + "external_dependencies": {"python": [], "bin": []}, + "data": [ + 'template.xml', + 'view.xml', + ], + "qweb": [ + 'static/src/xml/mail_private.xml', + ], + "demo": [], + + "post_load": None, + "pre_init_hook": None, + "post_init_hook": None, + + "auto_install": False, + "installable": False, +} diff --git a/mail_private/doc/changelog.rst b/mail_private/doc/changelog.rst new file mode 100644 index 0000000..5f35720 --- /dev/null +++ b/mail_private/doc/changelog.rst @@ -0,0 +1,7 @@ +Updates +======= + +`1.0.0` +------- + +- Init version diff --git a/mail_private/doc/index.rst b/mail_private/doc/index.rst new file mode 100644 index 0000000..4a0ec62 --- /dev/null +++ b/mail_private/doc/index.rst @@ -0,0 +1,26 @@ +==================== + Internal Messaging +==================== + +Installation +============ + +Nothing special is needed to install this module. + +Usage +===== + +To send a message to specified recipients: + +* Click on the ``[Send internal message]``. + +* Choose the recipients that should receive a message by ticking the checkboxes. + +* To add more recipients click on the ``[Open the full mail composer]`` on the right upper corner of the message block and choose recipients in the **Recipient** field. + +* Click on the ``[Send]`` button. + +Uninstallation +============== + +Nothing special is needed to uninstall this module. diff --git a/mail_private/images/mail_private_image.png b/mail_private/images/mail_private_image.png new file mode 100644 index 0000000..db45b79 Binary files /dev/null and b/mail_private/images/mail_private_image.png differ diff --git a/mail_private/models.py b/mail_private/models.py new file mode 100644 index 0000000..2d4b7ef --- /dev/null +++ b/mail_private/models.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from openerp.osv import osv, fields + + +class MailComposeMessage(osv.TransientModel): + _inherit = 'mail.compose.message' + + _columns = { + 'private': fields.boolean('Send Internal Message'), + } diff --git a/mail_private/static/description/check_recipients.png b/mail_private/static/description/check_recipients.png new file mode 100644 index 0000000..3eb915d Binary files /dev/null and b/mail_private/static/description/check_recipients.png differ diff --git a/mail_private/static/description/icon.png b/mail_private/static/description/icon.png new file mode 100644 index 0000000..79f7d8f Binary files /dev/null and b/mail_private/static/description/icon.png differ diff --git a/mail_private/static/description/index.html b/mail_private/static/description/index.html new file mode 100644 index 0000000..534319e --- /dev/null +++ b/mail_private/static/description/index.html @@ -0,0 +1,67 @@ +
+
+
+

Internal Messaging

+

Send private messages to specified recipients, regardless of who are in followers list.

+
+
+
+ +
+
+
+
+ The module will be available for Odoo 9.0 soon. You have time to buy Internal Messaging before price increase! (When you purchase 8.0 version, you get a right to download 9.0 and any further module versions). +
+ +

+ By default, to send a private message to specific recipient(s) you need to delete other followers included to the same document. The module allows to send private messages to recipients you specified, regardless of who are in the followers list. +

+

+ It simplify internal communication in leads, when you need to send some private messages to your colleagues before reply to customer. +It helps in records like project tasks too. In a task with many participants, you can easily send message and only specified colleagues will be notified. It allows to have clean inbox for everybody. It's very essential, because people often neglect important message in Inbox full of useless messages. +

+
+
+
+ +
+
+
+

How it works

+

+ Click on the "Send internal message" and choose the recipient(s). Then type any message and click on the "Send" button. +

+
+ +
+
+
+
+ +
+
+
+

+ As a result you can see the message with a dark background, which is sent to the corresponding recipient(s). Other followers of the document will not receive your message. +

+
+ +
+
+
+
+ + +
+
+
+

Need our service?

+

Contact us by email or fill out request form

+ +
+
+
diff --git a/mail_private/static/description/result_message.png b/mail_private/static/description/result_message.png new file mode 100644 index 0000000..17e640c Binary files /dev/null and b/mail_private/static/description/result_message.png differ diff --git a/mail_private/static/src/js/mail_private.js b/mail_private/static/src/js/mail_private.js new file mode 100644 index 0000000..bc0d781 --- /dev/null +++ b/mail_private/static/src/js/mail_private.js @@ -0,0 +1,201 @@ +openerp.mail_private = function(instance){ + + var mail = instance.mail; + + instance.mail.ThreadComposeMessage.include({ + init: function (parent, datasets, options) { + this._super.apply(this, arguments); + this.private = false; + }, + bind_events: function(){ + var self = this; + this.$('.oe_compose_post_private').on('click', self.on_toggle_quick_composer_private); + this._super.apply(this, arguments); + }, + on_compose_fullmail: function (default_composition_mode) { + var self = this; + if(!this.do_check_attachment_upload()) { + return false; + } + var recipient_done = $.Deferred(); + if (this.is_log) { + recipient_done.resolve([]); + } + else { + recipient_done = this.check_recipient_partners(); + } + $.when(recipient_done).done(function (partner_ids) { + var context = { + 'default_parent_id': self.id, + 'default_body': mail.ChatterUtils.get_text2html(self.$el ? (self.$el.find('textarea:not(.oe_compact)').val() || '') : ''), + 'default_attachment_ids': _.map(self.attachment_ids, function (file) {return file.id;}), + 'default_partner_ids': partner_ids, + 'default_is_log': self.is_log, + 'default_private': self.private, + 'mail_post_autofollow': true, + 'mail_post_autofollow_partner_ids': partner_ids, + 'is_private': self.is_private + }; + if (default_composition_mode != 'reply' && self.context.default_model && self.context.default_res_id) { + context.default_model = self.context.default_model; + context.default_res_id = self.context.default_res_id; + } + + var action = { + type: 'ir.actions.act_window', + res_model: 'mail.compose.message', + view_mode: 'form', + view_type: 'form', + views: [[false, 'form']], + target: 'new', + context: context, + }; + + self.do_action(action, { + 'on_close': function(){ !self.parent_thread.options.view_inbox && self.parent_thread.message_fetch(); } + }); + self.on_cancel(); + }); + + }, + do_send_message_post: function (partner_ids, log) { + var self = this; + var values = { + 'body': this.$('textarea').val(), + 'subject': false, + 'parent_id': this.context.default_parent_id, + 'attachment_ids': _.map(this.attachment_ids, function (file) {return file.id;}), + 'partner_ids': partner_ids, + 'context': _.extend(this.parent_thread.context, { + 'mail_post_autofollow': true, + 'mail_post_autofollow_partner_ids': partner_ids, + 'default_private': self.private + }), + 'type': 'comment', + 'content_subtype': 'plaintext', + }; + if (log || self.private) { + values.subtype = false; + } + else { + values.subtype = 'mail.mt_comment'; + } + this.parent_thread.ds_thread._model.call('message_post', [this.context.default_res_id], values).done(function (message_id) { + var thread = self.parent_thread; + var root = thread == self.options.root_thread; + if (self.options.display_indented_thread < self.thread_level && thread.parent_message) { + var thread = thread.parent_message.parent_thread; + } + // create object and attach to the thread object + thread.message_fetch([["id", "=", message_id]], false, [message_id], function (arg, data) { + var message = thread.create_message_object( data.slice(-1)[0] ); + // insert the message on dom + thread.insert_message( message, root ? undefined : self.$el, root ); + }); + self.on_cancel(); + self.flag_post = false; + }); + }, + on_toggle_quick_composer: function (event) { + if (event.target.className == 'oe_compose_post') { + this.recipients = []; + this.private = false; + } + this._super.apply(this, arguments); + }, + on_toggle_quick_composer_private: function (event) { + this.recipients = []; + var self = this; + var $input = $(event.target); + this.compute_emails_from(); + var email_addresses = _.pluck(this.recipients, 'email_address'); + var suggested_partners = $.Deferred(); + + // if clicked: call for suggested recipients + if (event.type == 'click') { + this.private = $input.hasClass('oe_compose_post_private'); + this.is_log = false; + suggested_partners = this.parent_thread.get_recipients_for_internal_message([this.context.default_res_id], this.context) + .done(function (additional_recipients) { + var thread_recipients = additional_recipients[self.context.default_res_id]; + _.each(thread_recipients, function (recipient) { + var parsed_email = mail.ChatterUtils.parse_email(recipient[1]); + if (_.indexOf(email_addresses, parsed_email[1]) == -1) { + self.recipients.push({ + 'checked': false, + 'partner_id': recipient[0], + 'full_name': recipient[1], + 'name': parsed_email[0], + 'email_address': parsed_email[1], + 'reason': recipient[2], + }); + } + }); + }); + } + else { + suggested_partners.resolve({}); + } + // uncheck partners from compute_emails_from + _.each(this.recipients, function(r){ + if (!r.partner_id){ + r.checked = false; + } + }); + + // when call for suggested partners finished: re-render the widget + $.when(suggested_partners).pipe(function (additional_recipients) { + if ((!self.stay_open || (event && event.type == 'click')) && (!self.show_composer || !self.$('textarea:not(.oe_compact)').val().match(/\S+/) && !self.attachment_ids.length)) { + self.show_composer = !self.show_composer || self.stay_open; + self.reinit(); + } + if (!self.stay_open && self.show_composer && (!event || event.type != 'blur')) { + self.$('textarea:not(.oe_compact):first').focus(); + } + }); + + return suggested_partners; + } + }); + + instance.mail.Thread.include({ + get_recipients_for_internal_message: function(ids, context){ + var self = this; + self.result = {}; + return new instance.web.Model(context.default_model).call( + 'read', [ids, ['message_follower_ids', 'partner_id'], context] + ).then(function (thread) { + for (var i = 0; i < thread.length; i++) { + var res_id = thread[i].id; + var recipient_ids = thread[i].message_follower_ids; + self.result[res_id] = []; + + // Add customer + self.customer = thread[i].partner_id; + if (self.customer && !recipient_ids.includes(self.customer[0])) { + recipient_ids.splice(0, 0, self.customer[0]); + } + + return new instance.web.Model('res.partner').call( + 'read', [recipient_ids, ['name', 'email', 'user_ids']] + ).then(function (res_partners){ + for (var j = 0; j < res_partners.length; j++) { + var partner = res_partners[j]; + if (!_.include(partner.user_ids, self.session.uid)){ + var reason = 'Follower'; + if (self.customer && partner.id == self.customer[0]){ + reason = 'Partner'; + } + self.result[res_id].push( + [partner.id, partner.name + '<' + partner.email + '>', reason] + ); + } + } + return self.result; + }); + } + return self.result; + }); + } + }); +}; diff --git a/mail_private/static/src/xml/mail_private.xml b/mail_private/static/src/xml/mail_private.xml new file mode 100644 index 0000000..d603d82 --- /dev/null +++ b/mail_private/static/src/xml/mail_private.xml @@ -0,0 +1,21 @@ + + diff --git a/mail_private/template.xml b/mail_private/template.xml new file mode 100644 index 0000000..534e53c --- /dev/null +++ b/mail_private/template.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/mail_private/view.xml b/mail_private/view.xml new file mode 100644 index 0000000..53232fa --- /dev/null +++ b/mail_private/view.xml @@ -0,0 +1,27 @@ + + + + + mail.private.mail.compose.message.form + mail.compose.message + + + +
+ + + + Email mass mailing on + the selected records + the current search filter. + + Followers of the document and + +
+
+
+
+
+
\ No newline at end of file diff --git a/mail_recovery/doc/changelog.rst b/mail_recovery/doc/changelog.rst index e2b0277..5f35720 100644 --- a/mail_recovery/doc/changelog.rst +++ b/mail_recovery/doc/changelog.rst @@ -1,5 +1,5 @@ -Changelog -========= +Updates +======= `1.0.0` ------- diff --git a/mail_sent/doc/changelog.rst b/mail_sent/doc/changelog.rst index 7615117..2147d0e 100644 --- a/mail_sent/doc/changelog.rst +++ b/mail_sent/doc/changelog.rst @@ -1,7 +1,7 @@ .. _changelog: -Changelog -========= +Updates +======= `1.0.2` ------- diff --git a/mail_sent/models.py b/mail_sent/models.py index f8310be..6e215e1 100644 --- a/mail_sent/models.py +++ b/mail_sent/models.py @@ -28,5 +28,6 @@ class MailMessage(models.Model): class MailComposeMessage(models.TransientModel): + _inherit = 'mail.compose.message' sent = fields.Boolean('Sent', help='dummy field to fix inherit error') diff --git a/res_partner_mails_count/models.py b/res_partner_mails_count/models.py index dcd036a..4fad23e 100644 --- a/res_partner_mails_count/models.py +++ b/res_partner_mails_count/models.py @@ -3,7 +3,7 @@ from openerp import models, fields, api -class res_partner(models.Model): +class ResPartner(models.Model): _inherit = 'res.partner' mails_to = fields.Integer(compute="_mails_to") mails_from = fields.Integer(compute="_mails_from") diff --git a/res_partner_strip_email/models.py b/res_partner_strip_email/models.py index 2d1b493..d2406fe 100644 --- a/res_partner_strip_email/models.py +++ b/res_partner_strip_email/models.py @@ -4,18 +4,18 @@ from openerp import api from openerp import models -class res_partner_strip_email(models.Model): +class ResPartnerStripEmail(models.Model): _inherit = 'res.partner' @api.one def write(self, vals): vals = self._check_email_field(vals) - return super(res_partner_strip_email, self).write(vals) + return super(ResPartnerStripEmail, self).write(vals) @api.model def create(self, vals): vals = self._check_email_field(vals) - return super(res_partner_strip_email, self).create(vals) + return super(ResPartnerStripEmail, self).create(vals) def _check_email_field(self, vals): if vals.get('email'):