diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 91a1d4cf05d..c6933190d78 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -2173,9 +2173,9 @@ class AccountMoveLine(models.Model):
help="The bank statement used for bank reconciliation")
# ==== Tax fields ====
- tax_ids = fields.Many2many('account.tax', string='Taxes')
+ tax_ids = fields.Many2many('account.tax', string='Taxes', help="Taxes that apply on the base amount")
tax_line_id = fields.Many2one('account.tax', string='Originator tax', ondelete='restrict', store=True,
- compute='_compute_tax_line_id')
+ compute='_compute_tax_line_id', help="Indicates that this journal item is a tax line")
tax_base_amount = fields.Monetary(string="Base Amount", store=True,
currency_field='company_currency_id')
tax_exigible = fields.Boolean(string='Appears in VAT report', default=True,
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index 7fbb328c69d..d04a4b05520 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -454,7 +454,10 @@ class account_payment(models.Model):
move.line_ids.remove_move_reconcile()
move.button_cancel()
move.unlink()
- rec.state = 'cancelled'
+ rec.write({
+ 'state': 'cancelled',
+ 'move_name': False,
+ })
@api.multi
def unlink(self):
diff --git a/addons/account/models/account_reconcile_model.py b/addons/account/models/account_reconcile_model.py
index 2834791ec38..108d7b75550 100644
--- a/addons/account/models/account_reconcile_model.py
+++ b/addons/account/models/account_reconcile_model.py
@@ -580,6 +580,8 @@ class AccountReconcileModel(models.Model):
'''
if not self.match_total_amount:
return True
+ if not candidates:
+ return False
# Match total residual amount.
total_residual = 0.0
diff --git a/addons/account/views/res_config_settings_views.xml b/addons/account/views/res_config_settings_views.xml
index f557e54b64d..a58c574020b 100644
--- a/addons/account/views/res_config_settings_views.xml
+++ b/addons/account/views/res_config_settings_views.xml
@@ -331,7 +331,7 @@
test
', + channel_ids: [], + id: 2, + model: 'partner', + needaction_partner_ids: [3], + res_id: 2, + }; + const notifications = [ [['myDB', 'ir.needaction'], message] ]; + form.call('bus_service', 'trigger', 'notification', notifications); + await testUtils.nextTick(); + assert.strictEqual( + thread.getUnreadCounter(), + 1, + "document thread should have one unread message (not marked as read)"); + assert.containsOnce( + form, + '.o_thread_message', + "should have one message in the chatter"); + + form.destroy(); +}); + QUnit.test('chatter: post a message and switch in edit mode', async function (assert) { assert.expect(5); diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js index 8c65266a392..226fd1e398a 100644 --- a/addons/mail/static/tests/discuss_tests.js +++ b/addons/mail/static/tests/discuss_tests.js @@ -59,6 +59,7 @@ QUnit.module('Discuss', { type: 'integer', }, }, + records: [], }, 'res.partner': { fields: { @@ -1490,6 +1491,70 @@ QUnit.test('custom-named DM conversation', async function (assert) { discuss.destroy(); }); +QUnit.test('receive channel message notification then delayed needaction notification', async function (assert) { + assert.expect(3); + + const message = { + author_id: [5, 'Demo User'], + body: 'test
', + channel_ids: [1], + id: 100, + model: 'mail.channel', + needaction: true, + needaction_partner_ids: [3], + res_id: 1, + }; + this.data.initMessaging = { + channel_slots: { + channel_channel: [{ + id: 1, + channel_type: 'channel', + name: "general", + }], + }, + }; + + const discuss = await createDiscuss({ + context: {}, + params: {}, + data: this.data, + services: this.services, + session: { + partner_id: 3 + }, + }); + + const $inbox = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_inbox"]'); + assert.hasClass( + $inbox, + 'o_active', + "'Inbox' should be the currently active thread"); + assert.containsNone( + discuss, + '.o_thread_message', + "inbox should contain no messages initially"); + + // simulate new needaction message posted on channnel + this.data['mail.message'].records.push(message); + // simulate receiving channel notification + discuss.call('bus_service', 'trigger', 'notification', [ + [['myDB', 'mail.channel', 1], message] + ]); + // short delay after receiving needaction notification + await testUtils.nextTick(); + // simulate receiving needaction message notification after a short delay + discuss.call('bus_service', 'trigger', 'notification', [ + [['myDB', 'ir.needaction', 3], message] + ]); + await testUtils.nextTick(); + assert.containsOnce( + discuss, + '.o_thread_message', + "inbox should contain one message"); + + discuss.destroy(); +}); + QUnit.test('input not cleared on unresolved message_post rpc', async function (assert) { assert.expect(2); diff --git a/addons/mail/static/tests/systray/systray_activity_menu_tests.js b/addons/mail/static/tests/systray/systray_activity_menu_tests.js index b2f76feb832..7d24b5e05a4 100644 --- a/addons/mail/static/tests/systray/systray_activity_menu_tests.js +++ b/addons/mail/static/tests/systray/systray_activity_menu_tests.js @@ -153,7 +153,7 @@ QUnit.test('activity menu widget: activity menu with 3 records', async function }); QUnit.test('activity menu widget: activity view icon', async function (assert) { - assert.expect(10); + assert.expect(12); var self = this; var activityMenu = new ActivityMenu(); testUtils.mock.addMockEnvironment(activityMenu, { @@ -193,9 +193,15 @@ QUnit.test('activity menu widget: activity view icon', async function (assert) { // click on the "Issue" activity icon await testUtils.dom.click(activityMenu.$('.dropdown-toggle')); + assert.hasClass(activityMenu.$('.dropdown-menu'), 'show', + "dropdown should be expanded"); + await testUtils.dom.click(activityMenu.$(".o_mail_activity_action[data-model_name='Issue']")); + assert.doesNotHaveClass(activityMenu.$('.dropdown-menu'), 'show', + "dropdown should be collapsed"); // click on the "Note" activity icon + await testUtils.dom.click(activityMenu.$('.dropdown-toggle')); await testUtils.dom.click(activityMenu.$(".o_mail_activity_action[data-model_name='Note']")); assert.verifySteps([ diff --git a/addons/mail/static/tests/systray/systray_messaging_menu_tests.js b/addons/mail/static/tests/systray/systray_messaging_menu_tests.js index b31b12f15b3..610e689ae4a 100644 --- a/addons/mail/static/tests/systray/systray_messaging_menu_tests.js +++ b/addons/mail/static/tests/systray/systray_messaging_menu_tests.js @@ -920,5 +920,62 @@ QUnit.test('messaging menu widget: expand on thread preview', async function (as messagingMenu.destroy(); }); +QUnit.test('messaging menu widget: click twice preview on slow message_fetch should open chat window once', async function (assert) { + // This test assumes that a condition for opening chat window is to + // successfully fetch messages beforehand. + assert.expect(1); + + const self = this; + // Used to pause `message_fetch` after opening the messaging menu. + // This is necessary `message_fetch` on mailbox_inbox is required to + // display the previews. + let lockMessageFetch = false; + const messageFetchProm = testUtils.makeTestPromise(); + + const messagingMenu = new MessagingMenu(); + testUtils.addMockEnvironment(messagingMenu, { + services: this.services, + data: this.data, + session: { partner_id: 1 }, + async mockRPC(route, args) { + if (args.method === 'message_fetch' && lockMessageFetch) { + const _super = this._super.bind(this); + await messageFetchProm; + return _super(route, args); + } + if (args.method === 'channel_minimize') { + // called to detach thread in chat window + // simulate longpolling response with new chat window state + const channelInfo = { + ...self.data['mail.channel'].records[0], + is_minimized: true, + state: 'open', + }; + const notifications = [ [['myDB', 'res.partner'], channelInfo] ]; + messagingMenu.call('bus_service', 'trigger', 'notification', notifications); + } + return this._super(...arguments); + }, + }); + await messagingMenu.appendTo($('#qunit-fixture')); + + // Opening chat window 1st time from messaging menu (pending from `messageFetchDef`) + await testUtils.dom.click(messagingMenu.$('.dropdown-toggle')); + lockMessageFetch = true; + await testUtils.dom.click(messagingMenu.$('.o_mail_preview')); + // Click again on preview to open chat window + await testUtils.dom.click(messagingMenu.$('.dropdown-toggle')); + await testUtils.dom.click(messagingMenu.$('.o_mail_preview')); + messageFetchProm.resolve(); + await testUtils.nextTick(); + + assert.containsOnce( + $, + '.o_thread_window', + "should only display a single chat window"); + + messagingMenu.destroy(); +}); + }); }); diff --git a/addons/mail/wizard/mail_resend_message.py b/addons/mail/wizard/mail_resend_message.py index bcff9dc9840..5235220240e 100644 --- a/addons/mail/wizard/mail_resend_message.py +++ b/addons/mail/wizard/mail_resend_message.py @@ -48,7 +48,7 @@ class MailResendMessage(models.TransientModel): rec['mail_message_id'] = mail_message_id.id rec['partner_ids'] = partner_ids else: - raise UserError('No message_id found in context') + raise UserError(_('No message_id found in context')) return rec @api.multi diff --git a/addons/maintenance/models/maintenance.py b/addons/maintenance/models/maintenance.py index 3677527e17a..e5fddbd8550 100644 --- a/addons/maintenance/models/maintenance.py +++ b/addons/maintenance/models/maintenance.py @@ -43,7 +43,7 @@ class MaintenanceEquipmentCategory(models.Model): alias_id = fields.Many2one( 'mail.alias', 'Alias', ondelete='restrict', required=True, help="Email alias for this equipment category. New emails will automatically " - "create new maintenance request for this equipment category.") + "create a new equipment under this category.") fold = fields.Boolean(string='Folded in Maintenance Pipe', compute='_compute_fold', store=True) @api.multi diff --git a/addons/maintenance/views/maintenance_views.xml b/addons/maintenance/views/maintenance_views.xml index 332cceeb14d..14f17c71c3e 100644 --- a/addons/maintenance/views/maintenance_views.xml +++ b/addons/maintenance/views/maintenance_views.xml @@ -136,7 +136,7 @@Successfully achieved
+ +Successfully achieved
+Successfully failed
- Date of Certification: + Date of Certification: + Date of Failure:
diff --git a/addons/survey/views/survey_survey_views.xml b/addons/survey/views/survey_survey_views.xml index 3319f518623..0c2b219c411 100644 --- a/addons/survey/views/survey_survey_views.xml +++ b/addons/survey/views/survey_survey_views.xml @@ -146,7 +146,7 @@