From eca543ef409a416c9880ae66e1fdc768b770f8ef Mon Sep 17 00:00:00 2001 From: Pierre-Yves Dufays Date: Thu, 3 Feb 2022 07:21:19 +0000 Subject: [PATCH] [REM] mail, sms, snailmail: discard notifications without confirmation Skip the confirmation step when wanting to get rid of a notification for emails, sms or letters that were not sent. Technical: - All 3 wizards to confirm the discarding of the notification have been removed (the ones for mail, sms and letter) have been removed - The method to discard notification has been centralized in MailThread (notify_cancel_by_type in addons/mail/models/mail_thread.py) - This centralization has been done as well on the client code as the same server code is called whether it is a mail, a sms or a letter (in addons/mail/static/src/models/notification_group/notification_group.js) - Existing front and back test have been adapted - A test of notify_cancel_by_type for mail has been added (addons/test_mail/tests/test_mail_management.py) - add missing author_id in mail_notification demo data Task-2752190 closes odoo/odoo#84452 Related: odoo/upgrade#3246 Signed-off-by: Thibault Delavallee (tde) --- addons/mail/__manifest__.py | 1 - addons/mail/models/mail_thread.py | 42 +++++++++++++++ addons/mail/security/ir.model.access.csv | 1 - .../notification_group/notification_group.js | 5 +- .../static/src/models/notification_group.js | 19 +++---- .../mail/static/tests/helpers/mock_server.js | 30 +++++++++++ ...ification_list_notification_group_tests.js | 36 ++++--------- addons/mail/wizard/__init__.py | 1 - addons/mail/wizard/mail_resend_cancel.py | 38 ------------- .../mail/wizard/mail_resend_cancel_views.xml | 27 ---------- addons/sms/__manifest__.py | 3 +- addons/sms/data/mail_demo.xml | 4 ++ addons/sms/models/mail_thread.py | 8 +++ addons/sms/security/ir.model.access.csv | 1 - .../static/src/models/notification_group.js | 17 ------ ...ification_list_notification_group_tests.js | 47 ++++++---------- addons/sms/wizard/__init__.py | 1 - addons/sms/wizard/sms_cancel.py | 38 ------------- addons/sms/wizard/sms_cancel_views.xml | 27 ---------- addons/snailmail/__manifest__.py | 3 +- addons/snailmail/models/__init__.py | 1 + addons/snailmail/models/mail_thread.py | 25 +++++++++ addons/snailmail/security/ir.model.access.csv | 1 - .../static/src/models/notification_group.js | 17 ------ ...ification_list_notification_group_tests.js | 50 +++++++---------- addons/snailmail/wizard/__init__.py | 1 - .../wizard/snailmail_letter_cancel.py | 26 --------- .../wizard/snailmail_letter_cancel_views.xml | 27 ---------- addons/test_mail/tests/__init__.py | 1 + .../test_mail/tests/test_mail_management.py | 53 +++++++++++++++++++ .../tests/test_sms_management.py | 20 ++++--- 31 files changed, 226 insertions(+), 345 deletions(-) delete mode 100644 addons/mail/wizard/mail_resend_cancel.py delete mode 100644 addons/mail/wizard/mail_resend_cancel_views.xml delete mode 100644 addons/sms/wizard/sms_cancel.py delete mode 100644 addons/sms/wizard/sms_cancel_views.xml create mode 100644 addons/snailmail/models/mail_thread.py delete mode 100644 addons/snailmail/wizard/snailmail_letter_cancel.py delete mode 100644 addons/snailmail/wizard/snailmail_letter_cancel_views.xml create mode 100644 addons/test_mail/tests/test_mail_management.py diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py index cb4811b0a75..64abac84062 100644 --- a/addons/mail/__manifest__.py +++ b/addons/mail/__manifest__.py @@ -13,7 +13,6 @@ 'data/mail_groups.xml', 'wizard/mail_blacklist_remove_views.xml', 'wizard/mail_compose_message_views.xml', - 'wizard/mail_resend_cancel_views.xml', 'wizard/mail_resend_message_views.xml', 'wizard/mail_template_preview_views.xml', 'wizard/mail_wizard_invite_views.xml', diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index aea4cd8e184..4390c6fc82b 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -2105,6 +2105,48 @@ class MailThread(models.AbstractModel): # NOTIFICATION API # ------------------------------------------------------ + def _notify_cancel_by_type_generic(self, notification_type): + """ Standard implementation for canceling notifications by type that cancels notifications + * in 'bounce' and 'exception' status + * of the current user + * of the given type + * for mail_message related to the model implemented by this class + It also sends bus notifications to update status of notifications in the web client. + """ + author_id = self.env.user.partner_id.id + self._cr.execute(""" + SELECT notif.id, msg.id + FROM mail_notification notif + JOIN mail_message msg ON notif.mail_message_id = msg.id + WHERE notif.notification_type = %(notification_type)s + AND notif.author_id = %(author_id)s + AND notif.notification_status IN ('bounce', 'exception') + AND msg.model = %(model_name)s + """, {'model_name': self._name, 'author_id': author_id, 'notification_type': notification_type}) + records = self._cr.fetchall() + if records: + notif_ids, msg_ids = zip(*records) + msg_ids = list(set(msg_ids)) + if notif_ids: + self.env['mail.notification'].browse(notif_ids).sudo().write({'notification_status': 'canceled'}) + if msg_ids: + self.env['mail.message'].browse(msg_ids)._notify_message_notification_update() + return True + + @api.model + def notify_cancel_by_type(self, notification_type): + """ Subclasses must call this method and then + * either call the standard implementation _notify_cancel_by_type_generic + * or implements their own logic + """ + if not self.env.user.has_group('base.group_user'): + raise exceptions.AccessError(_("Access Denied")) + self.check_access_rights('read') + + if notification_type == 'email': + self._notify_cancel_by_type_generic('email') + return True + def _notify_thread(self, message, msg_vals=False, **kwargs): """ Main notification method. This method basically does two things diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 68dfa6872fd..33fefed4c3d 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -45,7 +45,6 @@ access_mail_blacklist_system,access_mail_blacklist_system,model_mail_blacklist,b access_mail_wizard_invite,access.mail.wizard.invite,model_mail_wizard_invite,base.group_user,1,1,1,0 access_mail_compose_message,access.mail.compose.message,model_mail_compose_message,base.group_user,1,1,1,0 access_mail_compose_message_portal,access.mail.compose.message.portal,model_mail_compose_message,base.group_portal,1,1,1,0 -access_mail_resend_cancel,access.mail.resend.cancel,model_mail_resend_cancel,base.group_user,1,1,1,0 access_mail_resend_message,access.mail.resend.message,model_mail_resend_message,base.group_user,1,1,1,0 access_mail_resend_partner,access.mail.resend.partner,model_mail_resend_partner,base.group_user,1,1,1,0 access_mail_template_preview,access.mail.template.preview,model_mail_template_preview,base.group_user,1,1,1,0 diff --git a/addons/mail/static/src/components/notification_group/notification_group.js b/addons/mail/static/src/components/notification_group/notification_group.js index 3ec1ef1ca34..1049ef85f6c 100644 --- a/addons/mail/static/src/components/notification_group/notification_group.js +++ b/addons/mail/static/src/components/notification_group/notification_group.js @@ -63,10 +63,7 @@ export class NotificationGroup extends Component { * @param {MouseEvent} ev */ _onClickMarkAsRead(ev) { - this.notificationGroupView.notificationGroup.openCancelAction(); - if (!this.messaging.device.isMobile) { - this.messaging.messagingMenu.close(); - } + this.notificationGroupView.notificationGroup.notifyCancel(); } } diff --git a/addons/mail/static/src/models/notification_group.js b/addons/mail/static/src/models/notification_group.js index 26c92a9eea1..f9342e9ec44 100644 --- a/addons/mail/static/src/models/notification_group.js +++ b/addons/mail/static/src/models/notification_group.js @@ -10,20 +10,13 @@ registerModel({ identifyingFields: ['res_model', 'res_id', 'notification_type'], recordMethods: { /** - * Opens the view that allows to cancel all notifications of the group. + * Cancel notifications of the group. */ - openCancelAction() { - if (this.notification_type !== 'email') { - return; - } - this.env.bus.trigger('do-action', { - action: 'mail.mail_resend_cancel_action', - options: { - additional_context: { - default_model: this.res_model, - unread_counter: this.notifications.length, - }, - }, + notifyCancel() { + this.env.services.rpc({ + model: this.res_model, + method: 'notify_cancel_by_type', + kwargs: { notification_type: this.notification_type }, }); }, /** diff --git a/addons/mail/static/tests/helpers/mock_server.js b/addons/mail/static/tests/helpers/mock_server.js index 729adabd337..de2921ac5cd 100644 --- a/addons/mail/static/tests/helpers/mock_server.js +++ b/addons/mail/static/tests/helpers/mock_server.js @@ -301,6 +301,9 @@ MockServer.include({ delete kwargs.context; return this._mockMailThreadMessagePost(args.model, [id], kwargs, context); } + if (args.method === 'notify_cancel_by_type') { + return this._mockMailThreadNotifyCancelByType(args.model, args.kwargs.notification_type); + } return this._super(route, args); }, /** @@ -2364,4 +2367,31 @@ MockServer.include({ starred_counter: this._getRecords('mail.message', [['starred_partner_ids', 'in', user.partner_id]]).length, }; }, + /** + * Simulate the `notify_cancel_by_type` on `mail.thread` . + * Note that this method is overridden by snailmail module but not simulated here. + */ + _mockMailThreadNotifyCancelByType(model, notificationType) { + // Query matching notifications + const notifications = this._getRecords('mail.notification', [ + ['notification_type', '=', notificationType], + ['notification_status', 'in', ['bounce', 'exception']], + ]).filter(notification => { + const message = this._getRecords('mail.message', [['id', '=', notification.mail_message_id]])[0]; + return message.model === model && message.author_id === this.currentPartnerId; + }); + // Update notification status + this._mockWrite('mail.notification', [ + notifications.map(notification => notification.id), + { notification_status: 'canceled' }, + ]); + // Send bus notifications to update status of notifications in the web client + this._widget.call('bus_service', 'trigger', 'notification', [{ + payload: { + elements: this._mockMailMessage_MessageNotificationFormat( + notifications.map(notification => notification.mail_message_id)), + }, + type: 'mail.message/notification_update', + }]); + }, }); diff --git a/addons/mail/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js index 3fc560a5379..0f6980297a0 100644 --- a/addons/mail/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js @@ -86,7 +86,7 @@ QUnit.test('notification group basic layout', async function (assert) { }); QUnit.test('mark as read', async function (assert) { - assert.expect(6); + assert.expect(2); const pyEnv = await startServer(); const mailChannelId1 = pyEnv['mail.channel'].create(); @@ -100,28 +100,9 @@ QUnit.test('mark as read', async function (assert) { pyEnv['mail.notification'].create({ mail_message_id: mailMessageId1, // id of the related message notification_status: 'exception', // necessary value to have a failure - notification_type: 'email', // expected failure type for email message + notification_type: 'email', }); - const bus = new Bus(); - bus.on('do-action', null, payload => { - assert.step('do_action'); - assert.strictEqual( - payload.action, - 'mail.mail_resend_cancel_action', - "action should be the one to cancel email" - ); - assert.strictEqual( - payload.options.additional_context.default_model, - 'mail.channel', - "action should have the group model as default_model" - ); - assert.strictEqual( - payload.options.additional_context.unread_counter, - 1, - "action should have the group notification length as unread_counter" - ); - }); - const { createNotificationListComponent } = await start({ env: { bus } }); + const { createNotificationListComponent } = await start(); await createNotificationListComponent(); assert.containsOnce( document.body, @@ -129,10 +110,13 @@ QUnit.test('mark as read', async function (assert) { "should have 1 mark as read button" ); - document.querySelector('.o_NotificationGroup_markAsRead').click(); - assert.verifySteps( - ['do_action'], - "should do an action to display the cancel email dialog" + await afterNextRender(() => { + document.querySelector('.o_NotificationGroup_markAsRead').click(); + }); + assert.containsNone( + document.body, + '.o_NotificationGroup', + "should have no notification group" ); }); diff --git a/addons/mail/wizard/__init__.py b/addons/mail/wizard/__init__.py index c5e1916d46d..c3791d014a6 100644 --- a/addons/mail/wizard/__init__.py +++ b/addons/mail/wizard/__init__.py @@ -5,7 +5,6 @@ from . import base_module_uninstall from . import base_partner_merge_automatic_wizard from . import mail_blacklist_remove from . import mail_compose_message -from . import mail_resend_cancel from . import mail_resend_message from . import mail_template_preview from . import mail_wizard_invite diff --git a/addons/mail/wizard/mail_resend_cancel.py b/addons/mail/wizard/mail_resend_cancel.py deleted file mode 100644 index 3d454694b9e..00000000000 --- a/addons/mail/wizard/mail_resend_cancel.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import _, api, fields, models - - -class MailResendCancel(models.TransientModel): - _name = 'mail.resend.cancel' - _description = 'Dismiss notification for resend by model' - - model = fields.Char(string='Model') - help_message = fields.Char(string='Help message', compute='_compute_help_message') - - @api.depends('model') - def _compute_help_message(self): - for wizard in self: - wizard.help_message = _("Are you sure you want to discard %s mail delivery failures? You won't be able to re-send these mails later!") % (wizard._context.get('unread_counter')) - - def cancel_resend_action(self): - author_id = self.env.user.partner_id.id - for wizard in self: - self._cr.execute(""" - SELECT notif.id, mes.id - FROM mail_notification notif - JOIN mail_message mes - ON notif.mail_message_id = mes.id - WHERE notif.notification_type = 'email' - AND notif.author_id = %(author_id)s - AND notif.notification_status IN ('bounce', 'exception') - AND mes.model = %(model_name)s - """, {'model_name': wizard.model, 'author_id': author_id}) - res = self._cr.fetchall() - notif_ids = [row[0] for row in res] - messages_ids = list(set([row[1] for row in res])) - if notif_ids: - self.env["mail.notification"].browse(notif_ids).sudo().write({'notification_status': 'canceled'}) - self.env["mail.message"].browse(messages_ids)._notify_message_notification_update() - return {'type': 'ir.actions.act_window_close'} diff --git a/addons/mail/wizard/mail_resend_cancel_views.xml b/addons/mail/wizard/mail_resend_cancel_views.xml deleted file mode 100644 index 83b2d02cc01..00000000000 --- a/addons/mail/wizard/mail_resend_cancel_views.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - mail.resend.cancel.view.form - mail.resend.cancel - - -
- - -

If you want to re-send them, click Cancel now, then click on the notification and review them one by one by clicking on the red envelope next to each message.

- Envelope Example -
-
- -
-
- - Discard mail delivery failures - mail.resend.cancel - ir.actions.act_window - form - new - -
diff --git a/addons/sms/__manifest__.py b/addons/sms/__manifest__.py index c8ffc63f7bb..9252e676299 100644 --- a/addons/sms/__manifest__.py +++ b/addons/sms/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'SMS gateway', - 'version': '2.2', + 'version': '2.3', 'category': 'Hidden/Tools', 'summary': 'SMS Text Messaging', 'description': """ @@ -20,7 +20,6 @@ The service is provided by the In App Purchase Odoo platform. ], 'data': [ 'data/ir_cron_data.xml', - 'wizard/sms_cancel_views.xml', 'wizard/sms_composer_views.xml', 'wizard/sms_template_preview_views.xml', 'wizard/sms_resend_views.xml', diff --git a/addons/sms/data/mail_demo.xml b/addons/sms/data/mail_demo.xml index 079c1bcec24..9a007a0066b 100644 --- a/addons/sms/data/mail_demo.xml +++ b/addons/sms/data/mail_demo.xml @@ -19,6 +19,7 @@ + email @@ -45,6 +46,7 @@ + sms @@ -61,6 +63,7 @@ + sms @@ -76,6 +79,7 @@ + sms diff --git a/addons/sms/models/mail_thread.py b/addons/sms/models/mail_thread.py index 3230d41d20a..136dbcd46b6 100644 --- a/addons/sms/models/mail_thread.py +++ b/addons/sms/models/mail_thread.py @@ -362,3 +362,11 @@ class MailThread(models.AbstractModel): sms_all.filtered(lambda sms: sms.state == 'outgoing').send(auto_commit=False, raise_exception=False) return True + + @api.model + def notify_cancel_by_type(self, notification_type): + super().notify_cancel_by_type(notification_type) + if notification_type == 'sms': + # TDE CHECK: delete pending SMS + self._notify_cancel_by_type_generic('sms') + return True diff --git a/addons/sms/security/ir.model.access.csv b/addons/sms/security/ir.model.access.csv index bf54348054c..cdda764ff8d 100644 --- a/addons/sms/security/ir.model.access.csv +++ b/addons/sms/security/ir.model.access.csv @@ -4,7 +4,6 @@ access_sms_sms_system,access.sms.sms.system,model_sms_sms,base.group_system,1,1, access_sms_template_all,access.sms.template.all,model_sms_template,,0,0,0,0 access_sms_template_user,access.sms.template.user,model_sms_template,base.group_user,1,0,0,0 access_sms_template_system,access.sms.template.system,model_sms_template,base.group_system,1,1,1,1 -access_sms_cancel,access.sms.cancel,model_sms_cancel,base.group_user,1,1,1,0 access_sms_composer,access.sms.composer,model_sms_composer,base.group_user,1,1,1,0 access_sms_resend_recipient,access.sms.resend.recipient,model_sms_resend_recipient,base.group_user,1,1,1,0 access_sms_resend,access.sms.resend,model_sms_resend,base.group_user,1,1,1,0 diff --git a/addons/sms/static/src/models/notification_group.js b/addons/sms/static/src/models/notification_group.js index c4a58b75a3a..4212941ded4 100644 --- a/addons/sms/static/src/models/notification_group.js +++ b/addons/sms/static/src/models/notification_group.js @@ -5,23 +5,6 @@ import { patchRecordMethods } from '@mail/model/model_core'; import '@mail/models/notification_group'; patchRecordMethods('NotificationGroup', { - /** - * @override - */ - openCancelAction() { - if (this.notification_type !== 'sms') { - return this._super(...arguments); - } - this.env.bus.trigger('do-action', { - action: 'sms.sms_cancel_action', - options: { - additional_context: { - default_model: this.res_model, - unread_counter: this.notifications.length, - }, - }, - }); - }, /** * @override */ diff --git a/addons/sms/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js b/addons/sms/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js index 88f2c09a908..ba11e68cc76 100644 --- a/addons/sms/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js +++ b/addons/sms/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js @@ -14,15 +14,16 @@ QUnit.module('notification_list_notification_group_tests.js', { }); QUnit.test('mark as read', async function (assert) { - assert.expect(6); + assert.expect(2); this.data['mail.message'].records.push( // message that is expected to have a failure { - id: 11, // random unique id, will be used to link failure to message - message_type: 'sms', // message must be sms (goal of the test) - model: 'mail.channel', // expected value to link message to channel - res_id: 31, // id of a random channel + author_id: this.data.currentPartnerId, + id: 11, + message_type: 'sms', + model: 'mail.channel', + res_id: 31, } ); this.data['mail.notification'].records.push( @@ -30,42 +31,24 @@ QUnit.test('mark as read', async function (assert) { { mail_message_id: 11, // id of the related message notification_status: 'exception', // necessary value to have a failure - notification_type: 'sms', // expected failure type for sms message + notification_type: 'sms', } ); - const bus = new Bus(); - bus.on('do-action', null, payload => { - assert.step('do_action'); - assert.strictEqual( - payload.action, - 'sms.sms_cancel_action', - "action should be the one to cancel sms" - ); - assert.strictEqual( - payload.options.additional_context.default_model, - 'mail.channel', - "action should have the group model as default_model" - ); - assert.strictEqual( - payload.options.additional_context.unread_counter, - 1, - "action should have the group notification length as unread_counter" - ); - }); - - const { createNotificationListComponent } = await start({ data: this.data, env: { bus } }); + const { afterNextRender, createNotificationListComponent } = await start({ data: this.data }); await createNotificationListComponent(); - assert.containsOnce( document.body, '.o_NotificationGroup_markAsRead', "should have 1 mark as read button" ); - document.querySelector('.o_NotificationGroup_markAsRead').click(); - assert.verifySteps( - ['do_action'], - "should do an action to display the cancel sms dialog" + await afterNextRender(() => { + document.querySelector('.o_NotificationGroup_markAsRead').click(); + }); + assert.containsNone( + document.body, + '.o_NotificationGroup', + "should have no notification group" ); }); diff --git a/addons/sms/wizard/__init__.py b/addons/sms/wizard/__init__.py index bd0a5c11647..950892f0468 100644 --- a/addons/sms/wizard/__init__.py +++ b/addons/sms/wizard/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from . import sms_cancel from . import sms_composer from . import sms_resend from . import sms_template_preview diff --git a/addons/sms/wizard/sms_cancel.py b/addons/sms/wizard/sms_cancel.py deleted file mode 100644 index ab75c2c6873..00000000000 --- a/addons/sms/wizard/sms_cancel.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import _, api, fields, models - - -class SMSCancel(models.TransientModel): - _name = 'sms.cancel' - _description = 'Dismiss notification for resend by model' - - model = fields.Char(string='Model', required=True) - help_message = fields.Char(string='Help message', compute='_compute_help_message') - - @api.depends('model') - def _compute_help_message(self): - for wizard in self: - wizard.help_message = _("Are you sure you want to discard %s SMS delivery failures? You won't be able to re-send these SMS later!") % (wizard._context.get('unread_counter')) - - def action_cancel(self): - # TDE CHECK: delete pending SMS - author_id = self.env.user.partner_id.id - for wizard in self: - self._cr.execute(""" -SELECT notif.id, msg.id -FROM mail_notification notif -JOIN mail_message msg - ON notif.mail_message_id = msg.id -WHERE notif.notification_type = 'sms' IS TRUE AND notif.notification_status IN ('bounce', 'exception') - AND msg.model = %s - AND msg.author_id = %s """, (wizard.model, author_id)) - res = self._cr.fetchall() - notif_ids = [row[0] for row in res] - message_ids = list(set([row[1] for row in res])) - if notif_ids: - self.env['mail.notification'].browse(notif_ids).sudo().write({'notification_status': 'canceled'}) - if message_ids: - self.env['mail.message'].browse(message_ids)._notify_message_notification_update() - return {'type': 'ir.actions.act_window_close'} diff --git a/addons/sms/wizard/sms_cancel_views.xml b/addons/sms/wizard/sms_cancel_views.xml deleted file mode 100644 index 31f9fa8fcba..00000000000 --- a/addons/sms/wizard/sms_cancel_views.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - sms.cancel.form - sms.cancel - - -
- - -

If you want to re-send them, click Cancel now, then click on the notification and review them one by one by clicking on the red icon next to each message.

-
-
- -
-
- - - Discard SMS delivery failures - sms.cancel - ir.actions.act_window - form - new - -
diff --git a/addons/snailmail/__manifest__.py b/addons/snailmail/__manifest__.py index 6b6cf33e515..1234223b99d 100644 --- a/addons/snailmail/__manifest__.py +++ b/addons/snailmail/__manifest__.py @@ -6,7 +6,7 @@ Allows users to send documents by post ===================================================== """, 'category': 'Hidden/Tools', - 'version': '0.3', + 'version': '0.4', 'depends': [ 'iap_mail', 'mail' @@ -16,7 +16,6 @@ Allows users to send documents by post 'views/report_assets.xml', 'views/snailmail_views.xml', 'wizard/snailmail_confirm_views.xml', - 'wizard/snailmail_letter_cancel_views.xml', 'wizard/snailmail_letter_format_error_views.xml', 'wizard/snailmail_letter_missing_required_fields_views.xml', 'security/ir.model.access.csv', diff --git a/addons/snailmail/models/__init__.py b/addons/snailmail/models/__init__.py index 3c7ec4a947b..da01330dbf0 100644 --- a/addons/snailmail/models/__init__.py +++ b/addons/snailmail/models/__init__.py @@ -8,3 +8,4 @@ from . import res_company from . import res_config_settings from . import res_partner from . import snailmail_letter +from . import mail_thread diff --git a/addons/snailmail/models/mail_thread.py b/addons/snailmail/models/mail_thread.py new file mode 100644 index 00000000000..8b72bb999c2 --- /dev/null +++ b/addons/snailmail/models/mail_thread.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models +from odoo import api + + +class MailThread(models.AbstractModel): + _inherit = 'mail.thread' + + def _notify_cancel_snail(self): + author_id = self.env.user.id + letters = self.env['snailmail.letter'].search([ + ('state', 'not in', ['sent', 'canceled', 'pending']), + ('user_id', '=', author_id), + ('model', '=', self._name) + ]) + letters.cancel() + + @api.model + def notify_cancel_by_type(self, notification_type): + super().notify_cancel_by_type(notification_type) + if notification_type == 'snail': + self._notify_cancel_snail() + return True diff --git a/addons/snailmail/security/ir.model.access.csv b/addons/snailmail/security/ir.model.access.csv index 3472196ad25..63c55aacfe7 100644 --- a/addons/snailmail/security/ir.model.access.csv +++ b/addons/snailmail/security/ir.model.access.csv @@ -1,6 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_snailmail_letter_user,snailmail.letter.user,model_snailmail_letter,base.group_user,1,1,1,0 access_snailmail_letter_system,snailmail.letter.system,model_snailmail_letter,base.group_system,1,1,1,1 -access_snailmail_letter_cancel,access.snailmail.letter.cancel,model_snailmail_letter_cancel,base.group_user,1,1,1,0 access_snailmail_letter_format_error,access.snailmail.letter.format.error,model_snailmail_letter_format_error,base.group_user,1,1,1,0 access_snailmail_letter_missing_required_fields,access.snailmail.letter.missing.required.fields,model_snailmail_letter_missing_required_fields,base.group_user,1,1,1,0 diff --git a/addons/snailmail/static/src/models/notification_group.js b/addons/snailmail/static/src/models/notification_group.js index 736c446e84f..e2ee613a832 100644 --- a/addons/snailmail/static/src/models/notification_group.js +++ b/addons/snailmail/static/src/models/notification_group.js @@ -5,23 +5,6 @@ import { patchRecordMethods } from '@mail/model/model_core'; import '@mail/models/notification_group'; patchRecordMethods('NotificationGroup', { - /** - * @override - */ - openCancelAction() { - if (this.notification_type !== 'snail') { - return this._super(...arguments); - } - this.env.bus.trigger('do-action', { - action: 'snailmail.snailmail_letter_cancel_action', - options: { - additional_context: { - default_model: this.res_model, - unread_counter: this.notifications.length, - }, - }, - }); - }, /** * @override */ diff --git a/addons/snailmail/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js b/addons/snailmail/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js index 9bea11a000d..8187d1d4f17 100644 --- a/addons/snailmail/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js +++ b/addons/snailmail/static/tests/qunit_suite_tests/components/notification_list_notification_group_tests.js @@ -13,53 +13,41 @@ QUnit.module('notification_list_notification_group_tests.js', { }); QUnit.test('mark as read', async function (assert) { - assert.expect(6); + assert.expect(2); + + // Note: The server code is too complex to be rewritten in javascript. + // Actually, the server rely on the model "snailmail.letter" to identify the notification to cancel + // The following code simulates the cancel of the notification without using "snailmail.letter" model // message that is expected to have a failure this.data['mail.message'].records.push({ - id: 11, // random unique id, will be used to link failure to message - message_type: 'snailmail', // message must be snailmail (goal of the test) - model: 'mail.channel', // expected value to link message to channel - res_id: 31, // id of a random channel + author_id: this.data.currentPartnerId, + id: 11, + message_type: 'snailmail', + model: 'mail.channel', + res_id: 31, }); // failure that is expected to be used in the test this.data['mail.notification'].records.push({ mail_message_id: 11, // id of the related message notification_status: 'exception', // necessary value to have a failure - notification_type: 'snail', // expected failure type for snailmail message + notification_type: 'snail', }); - const bus = new Bus(); - bus.on('do-action', null, payload => { - assert.step('do_action'); - assert.strictEqual( - payload.action, - 'snailmail.snailmail_letter_cancel_action', - "action should be the one to cancel letter" - ); - assert.strictEqual( - payload.options.additional_context.default_model, - 'mail.channel', - "action should have the group model as default_model" - ); - assert.strictEqual( - payload.options.additional_context.unread_counter, - 1, - "action should have the group notification length as unread_counter" - ); - }); - const { createNotificationListComponent } = await start({ data: this.data, env: { bus } }); + const { afterNextRender, createNotificationListComponent } = await start({ data: this.data }); await createNotificationListComponent(); - assert.containsOnce( document.body, '.o_NotificationGroup_markAsRead', "should have 1 mark as read button" ); - document.querySelector('.o_NotificationGroup_markAsRead').click(); - assert.verifySteps( - ['do_action'], - "should do an action to display the cancel letter dialog" + await afterNextRender(() => { + document.querySelector('.o_NotificationGroup_markAsRead').click(); + }); + assert.containsNone( + document.body, + '.o_NotificationGroup', + "should have no notification group" ); }); diff --git a/addons/snailmail/wizard/__init__.py b/addons/snailmail/wizard/__init__.py index de2f4be2c8c..3e4ec78d4c0 100644 --- a/addons/snailmail/wizard/__init__.py +++ b/addons/snailmail/wizard/__init__.py @@ -1,4 +1,3 @@ from . import snailmail_confirm -from . import snailmail_letter_cancel from . import snailmail_letter_format_error from . import snailmail_letter_missing_required_fields diff --git a/addons/snailmail/wizard/snailmail_letter_cancel.py b/addons/snailmail/wizard/snailmail_letter_cancel.py deleted file mode 100644 index 59d55325124..00000000000 --- a/addons/snailmail/wizard/snailmail_letter_cancel.py +++ /dev/null @@ -1,26 +0,0 @@ - -from odoo import _, api, fields, models - -class SnailmailLetterCancel(models.TransientModel): - _name = 'snailmail.letter.cancel' - _description = 'Dismiss notification for resend by model' - - model = fields.Char(string='Model') - help_message = fields.Char(string='Help message', compute='_compute_help_message') - - @api.depends('model') - def _compute_help_message(self): - for wizard in self: - wizard.help_message = _("Are you sure you want to discard %s snailmail delivery failures? You won't be able to re-send these letters later!") % (wizard._context.get('unread_counter')) - - def cancel_resend_action(self): - author_id = self.env.user.id - for wizard in self: - letters = self.env['snailmail.letter'].search([ - ('state', 'not in', ['sent', 'canceled', 'pending']), - ('user_id', '=', author_id), - ('model', '=', wizard.model) - ]) - for letter in letters: - letter.cancel() - return {'type': 'ir.actions.act_window_close'} diff --git a/addons/snailmail/wizard/snailmail_letter_cancel_views.xml b/addons/snailmail/wizard/snailmail_letter_cancel_views.xml deleted file mode 100644 index bff8492a84f..00000000000 --- a/addons/snailmail/wizard/snailmail_letter_cancel_views.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - snailmail.letter.cancel.form - snailmail.letter.cancel - - -
- - -

If you want to re-send them, click Cancel now, then click on the notification and review them one by one by clicking on the red paper-plane next to each message.

-
-
- -
-
- - - Discard snailmail delivery failures - snailmail.letter.cancel - ir.actions.act_window - form - new - -
diff --git a/addons/test_mail/tests/__init__.py b/addons/test_mail/tests/__init__.py index 8d34da5957e..bccb72c373e 100644 --- a/addons/test_mail/tests/__init__.py +++ b/addons/test_mail/tests/__init__.py @@ -18,3 +18,4 @@ from . import test_message_post from . import test_message_track from . import test_performance from . import test_ui +from . import test_mail_management diff --git a/addons/test_mail/tests/test_mail_management.py b/addons/test_mail/tests/test_mail_management.py new file mode 100644 index 00000000000..c80f57e0a99 --- /dev/null +++ b/addons/test_mail/tests/test_mail_management.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients +from odoo.tests import tagged + + +@tagged('mail_management') +class TestMailManagement(TestMailCommon, TestRecipients): + + @classmethod + def setUpClass(cls): + super(TestMailManagement, cls).setUpClass() + cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({'name': 'Test'}) + cls._reset_mail_context(cls.test_record) + cls.msg = cls.test_record.message_post(body='TEST BODY', author_id=cls.partner_employee.id) + cls.notif_p1 = cls.env['mail.notification'].create({ + 'author_id': cls.msg.author_id.id, + 'mail_message_id': cls.msg.id, + 'res_partner_id': cls.partner_1.id, + 'notification_type': 'email', + 'notification_status': 'exception', + 'failure_type': 'mail_smtp', + }) + cls.notif_p2 = cls.env['mail.notification'].create({ + 'author_id': cls.msg.author_id.id, + 'mail_message_id': cls.msg.id, + 'res_partner_id': cls.partner_2.id, + 'notification_type': 'email', + 'notification_status': 'bounce', + 'failure_type': 'unknown', + }) + cls.partner_3 = cls.env['res.partner'].create({ + 'name': 'Partner3', + 'email': 'partner3@example.com', + }) + cls.notif_p3 = cls.env['mail.notification'].create({ + 'author_id': cls.msg.author_id.id, + 'mail_message_id': cls.msg.id, + 'res_partner_id': cls.partner_3.id, + 'notification_type': 'email', + 'notification_status': 'sent', + 'failure_type': None, + }) + + def test_mail_notify_cancel(self): + self._reset_bus() + + self.test_record.with_user(self.user_employee).notify_cancel_by_type('email') + self.assertEqual((self.notif_p1 | self.notif_p2 | self.notif_p3).mapped('notification_status'), + ['canceled', 'canceled', 'sent']) + + self.assertMessageBusNotifications(self.msg) diff --git a/addons/test_mail_full/tests/test_sms_management.py b/addons/test_mail_full/tests/test_sms_management.py index 4c23005bafb..cffcbc733cc 100644 --- a/addons/test_mail_full/tests/test_sms_management.py +++ b/addons/test_mail_full/tests/test_sms_management.py @@ -58,6 +58,15 @@ class TestSMSActionsCommon(TestMailFullCommon, TestMailFullRecipients): @tagged('sms_management') class TestSMSActions(TestSMSActionsCommon): + def test_sms_notify_cancel(self): + self._reset_bus() + + with self.with_user('employee'): + self.test_record.with_user(self.env.user).notify_cancel_by_type('sms') + self.assertEqual((self.notif_p1 | self.notif_p2).mapped('notification_status'), ['canceled', 'canceled']) + + self.assertMessageBusNotifications(self.msg) + def test_sms_set_cancel(self): self._reset_bus() self.sms_p1.action_set_canceled() @@ -188,14 +197,3 @@ class TestSMSWizards(TestSMSActionsCommon): self.assertSMSNotification([{'partner': self.partner_1, 'state': 'sent'}], 'TEST BODY', self.msg, check_sms=True) self.assertSMSNotification([{'partner': self.partner_2, 'state': 'canceled', 'number': self.notif_p2.sms_number, 'failure_type': 'sms_credit'}], 'TEST BODY', self.msg, check_sms=False) self.assertMessageBusNotifications(self.msg) - - def test_sms_cancel(self): - self._reset_bus() - - with self.mockSMSGateway(), self.with_user('employee'): - wizard = self.env['sms.cancel'].with_context(default_model=self.msg.model).create({}) - wizard.action_cancel() - - self.assertEqual((self.notif_p1 | self.notif_p2).mapped('notification_status'), ['canceled', 'canceled']) - - self.assertMessageBusNotifications(self.msg)