diff --git a/addons/mail/static/src/components/attachment/attachment_tests.js b/addons/mail/static/src/components/attachment/attachment_tests.js index b698c042825..2eb43d96f82 100644 --- a/addons/mail/static/src/components/attachment/attachment_tests.js +++ b/addons/mail/static/src/components/attachment/attachment_tests.js @@ -595,6 +595,41 @@ QUnit.test('close attachment viewer', async function (assert) { ); }); +QUnit.test('clicking on the delete attachment button multiple times should do the rpc only once', async function (assert) { + assert.expect(2); + await this.start({ + async mockRPC(route, args) { + if (args.method === "unlink" && args.model === "ir.attachment") { + assert.step('attachment_unlink'); + return; + } + return this._super(...arguments); + }, + }); + const attachment = this.env.models['mail.attachment'].create({ + filename: "test.txt", + id: 750, + mimetype: 'text/plain', + name: "test.txt", + }); + await this.createAttachmentComponent(attachment, { + detailsMode: 'hover', + }); + await afterNextRender(() => { + document.querySelector('.o_Attachment_actionUnlink').click(); + }); + + await afterNextRender(() => { + document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click(); + document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click(); + document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click(); + }); + assert.verifySteps( + ['attachment_unlink'], + "The unlink method must be called once" + ); +}); + }); }); }); diff --git a/addons/mail/static/src/models/attachment/attachment.js b/addons/mail/static/src/models/attachment/attachment.js index 03edf5abcab..dc4d59d52de 100644 --- a/addons/mail/static/src/models/attachment/attachment.js +++ b/addons/mail/static/src/models/attachment/attachment.js @@ -97,12 +97,20 @@ function factory(dependencies) { * Remove this attachment globally. */ async remove() { + if (this.isUnlinkPending) { + return; + } if (!this.isTemporary) { - await this.async(() => this.env.services.rpc({ - model: 'ir.attachment', - method: 'unlink', - args: [this.id], - }, { shadow: true })); + this.update({ isUnlinkPending: true }); + try { + await this.async(() => this.env.services.rpc({ + model: 'ir.attachment', + method: 'unlink', + args: [this.id], + }, { shadow: true })); + } finally { + this.update({ isUnlinkPending: false }); + } } else if (this.uploadingAbortController) { this.uploadingAbortController.abort(); } @@ -335,6 +343,12 @@ function factory(dependencies) { compute: '_computeIsTextFile', dependencies: ['fileType'], }), + /** + * True if an unlink RPC is pending, used to prevent multiple unlink attempts. + */ + isUnlinkPending: attr({ + default: false, + }), isViewable: attr({ compute: '_computeIsViewable', dependencies: [