[IMP] mail: improve notification management, either email either inbox

Currently partners have a boolean field to choose whether to receive
notifications only in their Odoo inbox or to receive them in their inbox
and by email. This leads to several issues :

 * if a customer is configured to not receive emails he will not receive
   any notification on sales orders, leads, ... This is not clearly
   indicated to the salesman and it is not easy to know how to change
   that behavior
 * if an user chooses to receive emails and does not use its inbox a lot
   of notifications stay in Odoo. The user has to manually set them as
   done to make them disappear which is redundant.

This commit changes that behavior. From now on customers will always
receive all notifications by email. Indeed Odoo is not a customer oriented
mailbox. Moreover sales orders or discussions on leads send to customers
should always be sent by email as it is the standard communication
mechanism. Users will be able to choose to receive notifications in Odoo
or by email. The choice is no longer inbox or inbox + email, but inbox
or email. Choosing one option or the other one depends on the way the
user wants to work.

Technically the field is moved on the users model and selection keys
are renamed. Notification process is modified

 * notified_partner_ids contains as before specified recipients as well
   as followers matching the subtype
 * customers and users working with emails are notified. During that
   process customers notifications are marked as done to be able to
   track the email state without having needaction. Users notifications
   are currently deleted as we do not track their email state.

The removal of partner field implies changes in various addons that
define partner data with this field set in the values.
This commit is contained in:
Aline Preillon
2017-02-21 16:42:11 +01:00
committed by Thibault Delavallée
parent eb3a531666
commit 2950ffaa86
22 changed files with 53 additions and 129 deletions
@@ -25,21 +25,17 @@ class TestFiscalPosition(common.TransactionCase):
self.jc = self.res_partner.create(dict(
name="JCVD",
vat="BE0477472701",
notify_email="none",
country_id=be.id))
self.ben = self.res_partner.create(dict(
name="BP",
notify_email="none",
country_id=be.id))
self.george = self.res_partner.create(dict(
name="George",
vat="BE0477472701",
notify_email="none",
country_id=fr.id))
self.alberto = self.res_partner.create(dict(
name="Alberto",
vat="BE0477472701",
notify_email="none",
country_id=mx.id))
self.be_nat = self.fp.create(dict(
name="BE-NAT",
+1
View File
@@ -19,6 +19,7 @@ class TestCrm(BaseFunctionalTest):
'name': 'Riton La Chignole',
'login': 'riton',
'email': 'riton.salesman_all@example.com',
'notification_type': 'inbox',
'groups_id': [(6, 0, [user_group_employee.id, user_group_salesman_all.id])]
})
+3 -3
View File
@@ -27,9 +27,9 @@
<field name="priority">1000</field>
</record>
<!-- Admin should not receivve emails at creation -->
<record id="base.partner_root" model="res.partner">
<field name="notify_email">none</field>
<!-- Admin should not receive emails at creation -->
<record id="base.user_root" model="res.users">
<field name="notification_type">inbox</field>
</record>
<!-- Catchall Email Alias -->
+2 -68
View File
@@ -3,75 +3,9 @@
<data noupdate="1">
<!-- Update 'Demo user' and partners email preferences to avoid mail bombing -->
<record id="base.partner_demo" model="res.partner">
<!-- <record id="base.partner_demo" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_1" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_2" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_3" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_4" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_3" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_1" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_4" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_12" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_12" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_10" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_3" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_12" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_2" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_4" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_3" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_1" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_12" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_18" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_2" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_4" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_1" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_12" model="res.partner">
<field name="notify_email">none</field>
</record>
</record> -->
<!-- Thread: 1 incoming email -->
<record id="msg_discus5" model="mail.message">
+7 -2
View File
@@ -801,12 +801,17 @@ class Message(models.Model):
# those methods are called as SUPERUSER because portal users posting messages
# have no access to partner model. Maybe propagating a real uid could be necessary.
email_channels = channels_sudo.filtered(lambda channel: channel.email_send)
# make a dedicated search on res.users to avoid user_ids.notification_type that will be user_ids.id in [ARRAY]
notif_users = self.env['res.users'].sudo().search([
('partner_id', 'in', partners_sudo.ids),
('notification_type', '=', 'inbox')
])
partners_sudo.search([
'|',
('id', 'in', partners_sudo.ids),
('id', 'in', (partners_sudo - notif_users.mapped('partner_id')).ids),
('channel_ids', 'in', email_channels.ids),
('email', '!=', self_sudo.author_id and self_sudo.author_id.email or self_sudo.email_from),
('notify_email', '!=', 'none')])._notify(self, force_send=force_send, send_after_commit=send_after_commit, user_signature=user_signature)
])._notify(self, force_send=force_send, send_after_commit=send_after_commit, user_signature=user_signature)
channels_sudo._notify(self)
# Discard cache, because child / parent allow reading and therefore
+1 -8
View File
@@ -19,14 +19,6 @@ class Partner(models.Model):
_mail_mass_mailing = _('Customers')
message_bounce = fields.Integer('Bounce', help="Counter of the number of bounced emails for this contact")
notify_email = fields.Selection([
('none', 'Never'),
('always', 'All Messages')],
'Email Messages and Notifications', required=True,
oldname='notification_email_send', default='always',
help="Policy to receive emails for new messages pushed to your personal Inbox:\n"
"- Never: no emails are sent\n"
"- All Messages: for every notification you receive in your Inbox")
opt_out = fields.Boolean(
'Opt-Out', help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. "
"Filter 'Available for Mass Mailing' allows users to filter the partners when performing mass mailing.")
@@ -140,6 +132,7 @@ class Partner(models.Model):
('res_partner_id', 'in', email.recipient_ids.ids)])
notifications.write({
'is_email': True,
'is_read': True, # handle by email discards Inbox notification
'email_status': 'ready',
})
+9 -2
View File
@@ -23,6 +23,13 @@ class Users(models.Model):
('everyone', 'Everyone'),
('partners', 'Authenticated Partners'),
('followers', 'Followers only')], string='Alias Contact Security', related='alias_id.alias_contact')
notification_type = fields.Selection([
('email', 'Handle by Emails'),
('inbox', 'Handle in Odoo')],
'Notification Management', required=True, default='email',
help="Policy on how to handle Chatter notifications:\n"
"- Emails: notifications are sent to your email\n"
"- Odoo: notifications appear in your Odoo Inbox")
def __init__(self, pool, cr):
""" Override of __init__ to add access rights on notification_email_send
@@ -32,10 +39,10 @@ class Users(models.Model):
init_res = super(Users, self).__init__(pool, cr)
# duplicate list to avoid modifying the original reference
type(self).SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS)
type(self).SELF_WRITEABLE_FIELDS.extend(['notify_email'])
type(self).SELF_WRITEABLE_FIELDS.extend(['notification_type'])
# duplicate list to avoid modifying the original reference
type(self).SELF_READABLE_FIELDS = list(self.SELF_READABLE_FIELDS)
type(self).SELF_READABLE_FIELDS.extend(['notify_email'])
type(self).SELF_READABLE_FIELDS.extend(['notification_type'])
return init_res
@api.model
@@ -50,8 +50,7 @@ var FieldMany2ManyTagsEmail = form_relational.FieldMany2ManyTags.extend({
var self = this;
new Model('res.partner').call("search", [[
["id", "in", ids],
["email", "=", false],
["notify_email", "=", 'always'] ]],
["email", "=", false] ]],
{context: this.build_context()})
.then(function (record_ids) {
// valid partner
+5 -7
View File
@@ -21,21 +21,21 @@ class BaseFunctionalTest(common.SavepointCase):
'login': 'ernest',
'email': 'e.e@example.com',
'signature': '--\nErnest',
'notify_email': 'always',
'notification_type': 'email',
'groups_id': [(6, 0, [user_group_employee.id])]})
cls.user_public = Users.create({
'name': 'Bert Tartignole',
'login': 'bert',
'email': 'b.t@example.com',
'signature': 'SignBert',
'notify_email': 'always',
'notification_type': 'email',
'groups_id': [(6, 0, [user_group_public.id])]})
cls.user_portal = Users.create({
'name': 'Chell Gladys',
'login': 'chell',
'email': 'chell@gladys.portal',
'signature': 'SignChell',
'notify_email': 'always',
'notification_type': 'email',
'groups_id': [(6, 0, [user_group_portal.id])]})
cls.user_admin = cls.env.user
@@ -86,12 +86,10 @@ class TestMail(BaseFunctionalTest):
# Test Data for Partners
cls.partner_1 = cls.env['res.partner'].create({
'name': 'Valid Lelitre',
'email': 'valid.lelitre@agrolait.com',
'notify_email': 'always'})
'email': 'valid.lelitre@agrolait.com'})
cls.partner_2 = cls.env['res.partner'].create({
'name': 'Valid Poilvache',
'email': 'valid.other@gmail.com',
'notify_email': 'always'})
'email': 'valid.other@gmail.com'})
TestModel = cls.env['mail.test'].with_context({
'mail_create_nolog': True,
+8 -4
View File
@@ -26,6 +26,9 @@ class TestMailFeatures(TestMail):
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_needaction(self):
# needaction use Inbox notification
(self.user_employee | self.user_admin).write({'notification_type': 'inbox'})
na_emp1_base = self.test_pigs.sudo(self.user_employee).message_needaction_counter
na_emp2_base = self.test_pigs.sudo().message_needaction_counter
@@ -97,9 +100,9 @@ class TestMessagePost(TestMail):
'name': 'Attach2', 'datas_fname': 'Attach2',
'datas': 'bWlncmF0aW9uIHRlc3Q=',
'res_model': 'mail.compose.message', 'res_id': 0})
# partner_2 does not want to receive notification email
self.partner_2.write({'notify_email': 'none'})
self.user_admin.write({'notify_email': 'always'})
self.user_admin.write({'notification_type': 'email'})
# subscribe second employee to the group to test notifications
self.test_pigs.message_subscribe_users(user_ids=[self.env.user.id])
@@ -134,12 +137,13 @@ class TestMessagePost(TestMail):
self.assertFalse(self.env['mail.mail'].search([('mail_message_id', '=', msg.message_id)]),
'message_post: mail.mail notifications should have been auto-deleted')
# notification emails: followers + recipients - notify_email=none (partner_2) - author (user_employee)
# notification emails: followers + recipients - author (user_employee)
self.assertEqual(set(m['email_from'] for m in self._mails),
set(['%s <%s>' % (self.user_employee.name, self.user_employee.email)]),
'message_post: notification email wrong email_from: should use sender email')
self.assertEqual(set(m['email_to'][0] for m in self._mails),
set(['%s <%s>' % (self.partner_1.name, self.partner_1.email),
'%s <%s>' % (self.partner_2.name, self.partner_2.email),
'%s <%s>' % (self.env.user.name, self.env.user.email)]))
self.assertFalse(any(len(m['email_to']) != 1 for m in self._mails),
'message_post: notification email should be sent to one partner at a time')
+1 -1
View File
@@ -10,7 +10,7 @@ class TestMessageRead(TestMail):
@mute_logger('odoo.addons.mail.models.mail_mail')
def setUp(self):
super(TestMessageRead, self).setUp()
self.user_employee.write({'notify_email': 'none'})
self.user_employee.write({'notification_type': 'inbox'})
self.group_pigs.message_subscribe_users([self.user_employee.id])
self.msg_0 = self.group_pigs.message_post(body='0', subtype='mt_comment')
self.msg_1 = self.group_pigs.message_post(body='1', subtype='mt_comment')
-1
View File
@@ -8,7 +8,6 @@
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='user_id']" position="after">
<field name='notify_email' widget="radio" groups="base.group_no_one"/>
<field name="opt_out" string="Mailing Opt-Out" groups="base.group_no_one"/>
<field name="message_bounce" groups="base.group_no_one"/>
</xpath>
+2 -2
View File
@@ -10,7 +10,7 @@
<field name="arch" type="xml">
<data>
<field name="email" position="before">
<field name="notify_email" widget="radio" readonly="0"/>
<field name="notification_type" widget="radio" readonly="0"/>
</field>
</data>
</field>
@@ -24,7 +24,7 @@
<field name="arch" type="xml">
<data>
<field name="signature" position="before">
<field name="notify_email" widget="radio"/>
<field name="notification_type" widget="radio"/>
</field>
<field name="signature" position="before">
<field name="alias_id"/>
+2 -2
View File
@@ -24,13 +24,13 @@ class TestMrpCommon(common2.TestStockCommon):
'name': 'Hilda Ferachwal',
'login': 'hilda',
'email': 'h.h@example.com',
'notify_email': 'none',
'notification_type': 'inbox',
'groups_id': [(6, 0, [user_group_mrp_user.id])]})
cls.user_mrp_manager = Users.create({
'name': 'Gary Youngwomen',
'login': 'gary',
'email': 'g.g@example.com',
'notify_email': 'none',
'notification_type': 'inbox',
'groups_id': [(6, 0, [user_group_mrp_manager.id])]})
cls.workcenter_1 = cls.env['mrp.workcenter'].create({
+1 -1
View File
@@ -8,7 +8,6 @@
<field name="email">demo.portal@yourcompany.example.com</field>
<field name="supplier" eval="False"/>
<field name="customer" eval="True"/>
<field name="notify_email">none</field>
<field name="city">Vivegnis</field>
<field name="zip">4683</field>
<field name="country_id" ref="base.be"/>
@@ -19,6 +18,7 @@
<field name="partner_id" ref="partner_demo_portal"/>
<field name="login">portal</field>
<field name="password">portal</field>
<field name="notification_type">inbox</field>
<field name="signature"><![CDATA[<span>-- <br/>
Mr Demo Portal</span>]]></field>
<!-- Avoid auto-including this user in any default group -->
+1 -1
View File
@@ -23,5 +23,5 @@ class TestStockCommon(common.TestProductCommon):
'name': 'Fabricette Manivelle',
'login': 'fabricette',
'email': 'f.f@example.com',
'notify_email': 'none',
'notification_type': 'inbox',
'groups_id': [(6, 0, [user_group_employee.id])]})
+1 -1
View File
@@ -15,7 +15,7 @@ class TestPortalProjectBase(TestProjectBase):
'login': 'noemie',
'email': 'n.n@example.com',
'signature': '--\nNoemie',
'notify_email': 'always',
'notification_type': 'email',
'groups_id': [(6, 0, [])]})
self.task_3 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
+1 -1
View File
@@ -20,7 +20,7 @@ class TestCreatePicking(common.TestProductCommon):
'name': 'Pauline Poivraisselle',
'login': 'pauline',
'email': 'pur@example.com',
'notify_email': 'none',
'notification_type': 'inbox',
'groups_id': [(6, 0, [res_users_purchase_user.id])]})
self.po_vals = {
+2 -2
View File
@@ -15,7 +15,7 @@ class TestSale(AccountingTestCase):
'login': 'manager',
'email': 'a.m@example.com',
'signature': '--\nAndreww',
'notify_email': 'always',
'notification_type': 'email',
'groups_id': [(6, 0, [group_manager.id])]
})
self.user = self.env['res.users'].create({
@@ -23,7 +23,7 @@ class TestSale(AccountingTestCase):
'login': 'user',
'email': 'm.u@example.com',
'signature': '--\nMark',
'notify_email': 'always',
'notification_type': 'email',
'groups_id': [(6, 0, [group_user.id])]
})
# create quotation with differend kinds of products (all possible combinations)
+2 -2
View File
@@ -59,13 +59,13 @@ class TestStockCommon(common.TestProductCommon):
'name': 'Pauline Poivraisselle',
'login': 'pauline',
'email': 'p.p@example.com',
'notify_email': 'none',
'notification_type': 'inbox',
'groups_id': [(6, 0, [user_group_stock_user.id])]})
cls.user_stock_manager = Users.create({
'name': 'Julie Tablier',
'login': 'julie',
'email': 'j.j@example.com',
'notify_email': 'none',
'notification_type': 'inbox',
'groups_id': [(6, 0, [user_group_stock_manager.id])]})
# Warehouses
+3 -3
View File
@@ -18,20 +18,20 @@ class TestWebsiteBlogCommon(common.TransactionCase):
'name': 'Armande Employee',
'login': 'armande',
'email': 'armande.employee@example.com',
'notify_email': 'none',
'notification_type': 'inbox',
'groups_id': [(6, 0, [group_employee_id])]
})
self.user_blogmanager = Users.with_context({'no_reset_password': True}).create({
'name': 'Bastien BlogManager',
'login': 'bastien',
'email': 'bastien.blogmanager@example.com',
'notify_email': 'none',
'notification_type': 'inbox',
'groups_id': [(6, 0, [group_blog_manager_id, group_employee_id])]
})
self.user_public = Users.with_context({'no_reset_password': True}).create({
'name': 'Cedric Public',
'login': 'cedric',
'email': 'cedric.public@example.com',
'notify_email': 'none',
'notification_type': 'inbox',
'groups_id': [(6, 0, [group_public_id])]
})
@@ -238,16 +238,4 @@ Our ERP expertise is since 2005 and we mainly focus on Odoo Implementation acros
Best Designers has now emerged serving the Small and Medium Business – SMB’s with lots of Free and Open Source Software's, Our team is proficient to deliver quick and quality solutions. With a blend of experts with proven track record, our enterprise solution Business Consultants, ERP Consultants, Network Engineers, Software Developers, Web Architects, Software Testers and Search Engine Optimizers makes it possible to offer our clients affordable solutions that are both innovative and highly influential.
]]></field>
</record>
<record id="base.res_partner_10" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_2" model="res.partner">
<field name="notify_email">none</field>
</record>
<record id="base.res_partner_1" model="res.partner">
<field name="notify_email">none</field>
</record>
</odoo>